/*
 Navicat Premium Data Transfer

 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80012
 Source Host           : localhost:3306
 Source Schema         : blogdev

 Target Server Type    : MySQL
 Target Server Version : 80012
 File Encoding         : 65001

 Date: 25/02/2019 13:49:17
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for article
-- ----------------------------
DROP TABLE IF EXISTS `article`;
CREATE TABLE `article` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text,
  `createdAt` datetime DEFAULT NULL,
  `updatedAt` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=71 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of article
-- ----------------------------
BEGIN;
INSERT INTO `article` VALUES (-1, '测试留言的文章！！！！', '不能删！！！数据库上有留言字段引用此ID', '2019-02-23 05:34:03', '2019-02-23 05:34:03');
INSERT INTO `article` VALUES (1, 'mysql - 数据库操作和数据属性', '## 数据库操作\n\n启动 `mysql`, `mac` 可通过 `brew` 安装 `mysql` 后启动。 `window` 需要手动配置\n\n```js\n// mac\nmysql.server start\n\n// window\nnet start mysql\n\n// 登录 -u 用户名 root 超级用户 -p 密码\nmysql -uroot -p\n\n// 退出\nmysql > \\q\n\n// 切换到 learn 数据库\nmysql > use learn\n```\n<!--more-->\n\nMySQL 语句规范\n\n- 关键字与函数名称全部大写\n- 数据库名称，表名称，字段名称全部小写\n- SQL 语句必须以分号结尾\n\n```js\n// 创建数据库 默认编码 utf-8\nCREATE DATABASE IF NOT EXISTS t1;\n\n// 创建 gbk 编码的数据库\nCREATE DATABASE IF NOT EXISTS t2 CHARACTER SET gbk;\n\n//显示创建数据库 t1 的指令\nSHOW CREATE DATABASE t1;\n\n// 查看数据库\nSHOW DATABASES;\n\n// 删除数据库\nDROP DATABASE IF EXISTS t1;\n```\n\n## 数据类型\n\n### 整型\n\n| MySQL 数据类型 | 含义     | （有符号）                   |\n| -------------- | -------- | ---------------------------- |\n| tinyint(m)     | 1 个字节 | 范围(-128~127)               |\n| smallint(m)    | 2 个字节 | 范围(-32768~32767)           |\n| mediumint(m)   | 3 个字节 | 范围(-8388608~8388607)       |\n| int(m)         | 4 个字节 | 范围(-2147483648~2147483647) |\n| bigint(m)      | 8 个字节 | 范围(+-9.22\\*10 的 18 次方)  |\n\n比如我们存储年龄，范围为 0-100 ，此时我们可以使用 `TINYINT` 存储\n\n\n### 浮点型(float和double)\n\n| MySQL数据类型 | 含义                                           |\n| ------------- | ---------------------------------------------- |\n| float(m,d)    | 单精度浮点型 8位精度(4字节)  m总个数，d小数位  |\n| double(m,d)   | 双精度浮点型 16位精度(8字节)  m总个数，d小数位 |\n\n设一个字段定义为float(6,3)，如果插入一个数123.45678,实际数据库里存的是123.457，但总个数还以实际为准，即6位。整数部分最大是3位，如果插入数12.123456，存储的是12.1234，如果插入12.12，存储的是12.1200.\n\n\n### 字符串(char,varchar,_text)\n\n| MySQL数据类型 | 含义                            |\n| ------------- | ------------------------------- |\n| char(n)       | 固定长度，最多255个字符         |\n| varchar(n)    | 固定长度，最多65535个字符       |\n| tinytext      | 可变长度，最多255个字符         |\n| text          | 可变长度，最多65535个字符       |\n| mediumtext    | 可变长度，最多2的24次方-1个字符 |\n| longtext      | 可变长度，最多2的32次方-1个字符 |\n\n\n`char`和`varchar`：\n\n1.`char(n)` 若存入字符数小于n，则以空格补于其后，查询之时再将空格去掉。所以char类型存储的字符串末尾不能有空格，`varchar`不限于此。 \n\n2.`char(n)` 固定长度，char(4)不管是存入几个字符，都将占用4个字节，`varchar`是存入的实际字符数+1个字节（n<=255）或2个字节(n>255)，\n\n所以`varchar`(4),存入3个字符将占用4个字节。 \n\n\n3.char类型的字符串检索速度要比`varchar`类型的快。\n`varchar`和`text`： \n\n1.`varchar`可指定n，`text`不能指定，内部存储`varchar`是存入的实际字符数+1个字节（n<=255）或2个字节(n>255)，`text`是实际字符数+2个字\n\n节。 \n\n2.`text`类型不能有默认值。 \n\n3.`varchar`可直接创建索引，`text`创建索引要指定前多少个字符。`varchar`查询速度快于`text`,在都创建索引的情况下，`text`的索引似乎不起作用。\n\n \n\n5.二进制数据(_Blob)\n\n1._BLOB和_text存储方式不同，_TEXT以文本方式存储，英文存储区分大小写，而_Blob是以二进制方式存储，不分大小写。\n\n2._BLOB存储的数据只能整体读出。 \n\n3._TEXT可以指定字符集，_BLO不用指定字符集。\n\n### 日期时间类型\n\n| MySQL数据类型 | 含义                          |\n| ------------- | ----------------------------- |\n| date          | 日期 \'2008-12-2\'              |\n| time          | 时间 \'12:25:36\'               |\n| datetime      | 日期时间 \'2008-12-2 22:06:44\' |\n| timestamp     | 自动存储记录修改时间          |\n\n## 数据类型的属性\n\n| MySQL关键字        | 含义                     |\n| ------------------ | ------------------------ |\n| NULL               | 数据列可包含NULL值       |\n| NOT NULL           | 数据列不允许包含NULL值   |\n| DEFAULT            | 默认值                   |\n| PRIMARY KEY        | 主键                     |\n| AUTO_INCREMENT     | 自动递增，适用于整数类型 |\n| UNSIGNED           | 无符号                   |\n| CHARACTER SET name | 指定一个字符集           |', '2019-02-11 12:19:35', '2019-02-11 12:19:35');
INSERT INTO `article` VALUES (2, 'mysql - 对 table 的操作', '## 创建表\n\n```sql\nmysql.server start // net start mysql\n\nmysql -uroot -p // login\n\nuse test // 进入数据库\n\nCREATE TABLE [IF NOT EXISTS] table_name (\n  column_name data_type,\n  // ...\n)\n```\n<!--more-->\n\n### demo1 \n\n创建 `tb1` 表\n\n```sql\nCREATE TABLE IF NOT EXISTS tb1 (\n  username VARCHAR(20) NOT NULL,\n  age TINYINT UNSIGNED,\n  salary FLOAT(8, 2) UNSIGNED\n);\n\nSHOIW TABLES; --查看数据库中的表\n\nSHOW COLUMNS FROM tb1; --查看数据表中的结构\n```\n\n| Field | Type  | Null | Key | Default | Extra |\n| :---: | :---: |:--: | :-: | :-----: | :---: |\n| username |     varchar(20)     | YES  |     |  null   |       |\n|   age    | tinyint(3) unsigned | YES  |     |  null   |       |\n|  salary  | float(8,2) unsigned | YES  |     |  null   |       |\n\n- username: 用户的名字往往是字符型，字符数据量小，所以数据类型定为 `VARCHAR(20)`, `NOT NULL` 不能为空\n- age: 年龄不能为负值且为整型，数据类型定为 `TINYINT`\n- salary: `FLOAT(8, 2)` 整数八位 - 小数有两位，非负值\n\n### demo2\n\n```sql\nCREATE TABLE IF NOT EXISTS tb3(\n  id SMALLINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  username VARCHAR(20) NOT NULL UNIQUE KEY,\n  sex ENUM (\'1\',\'2\',\'3\') DEFAULT \'3\'\n);\n\n-- AUTO_INCREMENT: 自增字段，必须为主键 `PRIMARY KEY`，保证记录的唯一性.\n-- UNIQUE KEY: 唯一\n-- DEFAULT: 默认\n```\n\n\n| Field | Type  | Null | Key | Default | Extra |\n| :---: | :---: |:--: | :-: | :-----: | :------------: |\n|    id    |    smallint(5)    |  NO  | PRI |  null   | auto_increment |\n| username |    varchar(20)    |  NO  |     |  null   |                |\n|   sex    | enum(\'1\',\'2\',\'3\') | YES  |     |    3    |                |\n\n\n## 约束\n\n- 约束保证数据的完整性和一致性。\n- 约束表现为表级约束和列级约束。\n- 约束类型包括\n  - `NOT NULL` 非空\n  - `PRIMARY KEY` 主键\n  - `UNIQUE` 唯一\n  - `DEFAULT` 默认\n  - `FOREIGN KEY` 外键 (foreign key)\n\n> 外键约束：保持数据一致性，完整性，实现一对多或者多对一的关系\n\n> 表级约束：针对两个或者两个以上的字段来使用\n\n> 列级约束：只针对某一个字段来使用\n\n### 外键约束\n\n1.  父表和子表必须使用相同的存储引擎，而且禁止使用临时表\n2.  数据表的存储引擎只能为 InnoDB\n3.  外键列和参照列必须具有相似的数据类型。其中数字的长度或是否有符号位必须相同；而字符的长度则可以不同。\n4.  外键列和参照列必须创建索引。如果外键列不存在索引的话，MySQL 将自动创建索引。\n\n```sql\n-- 身份表\nCREATE TABLE IF NOT EXISTS provinces (\n  id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n  pname VARCHAR(20) NOT NULL\n);\n\n-- 用户表\nCREATE TABLE IF NOT EXISTS users (\n   id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n   username VARCHAR(10) NOT NULL,\n   pid SMALLINT UNSIGNED,\n   FOREIGN KEY(pid) REFERENCES provinces(id) -- 设置 pid 外键 references provinces 表的 id\n);\n\n-- 外键列和参照列必须具有相似的数据类型: pid BIGINT， 是创建不成功的。保证数据类型相同是第一步\n-- 数字的长度或是否有符号位必须相同：pid SMALLINT, 同样创建不成功。有符号位位必须相同。pid SMALLINT UNSIGNED 就满足条件了\n-- 外键列和参照列必须创建索引:, 我们没有创建，MySQL 自动创建了索引。\n\n-- 主键在创建的同时，会自动创建索引。\nSHOW INDEXES FROM provinces\\G; -- Seq_in_index: 1\n```\n\n1. 我们创建了父表 `provinces`, 子表 `users`\n2. 外键列：pid , 参照列 id\n\n\n## 对 table column 的操作\n\n列的增加、删除，约束的添加、约束的删除。\n\n```sql\n-- 添加单列\nalter table tbl_name add [column] col_name col_difinition[first|after col_name]\n\n-- 解释：first 插入第一列，after col_name 插入某一列后面。省略不写，加在最后列\n\n-- 添加多列\nalter table tbl_name add [column] (col_name col_difinition,...)\n\n-- 删除列\nalter table tbl_name drop[column] col_name1,col_name2;\n\n\n-- demo\nALTER TABLE users ADD age TINYINT NOT NULL DEFAULT 10;\nALTER TABLE users ADD password VARCHAR(32) NOT NULL AFTER username;\nALTER TABLE users DROP password, DROP username;\n```\n\n```sql\n-- 修改数据表【添加或删除约束】：\n\nALTER TABLE table_name ADD [CONSTRAINT [symbol]] PRIMARY KEY [index_type](index_col_name,...) -- 这是添加主键约束(只能有一个)\n\nALTER TABLE table_name ADD [CONSTRAINT [symbol]] UNIQUE [INDEX/KEY] [index_name] [index_type] (index_col_name,...); --这是添加唯一约束(可以有多个)\n\nALTER TABLE table_name ADD [CONSTRAINT [symbol]] FOREIGN KEY [index_name] (index_col_name,...) reference_definition; --这是添加外键约束(可以有多个)\n\nALTER TABLE table_name ALTER [COLUMN] col_name {SET DEFAULT literal(这个literal的意思是加上的default)/DROP DEFAULT} --添加或删除默认约束\n\nALTER TABLE table_name DROP PRIMARY KEY; -- 删除主键约束\n\nALTER TABLE table_name DROP {INDEX/KEY} index_name; --删除唯一约束\n\nALTER TABLE table_name DROP FOREIGN KEY fk_symbol; --删除外键约束\n\n\n-- demo\nCREATE TABLE IF NOT EXISTS users2 (\n  username VARCHAR(10) NOT NULL,\n  pid SMALLINT UNSIGNED\n);\n\nALTER TABLE users2 ADD id SMALLINT UNSIGNED;\n\n-- 添加主键约束\nALTER TABLE users2 ADD CONSTRAINT PRIMARY KEY(id);\n\n-- 添加唯一约束\nALTER TABLE users2 ADD UNIQUE (username);\n\n-- 添加外键\nALTER TABLE users2 ADD FOREIGN KEY (pid) REFERENCES provinces (id);\n\n-- 添加默认约束\nALTER TABLE users2 ADD age TINYINT UNSIGNED NOT NULL;\n\nALTER TABLE users2 ALTER age SET DEFAULT 22;\n\n-- 删除默认约束\nALTER TABLE users2 ALTER age DROP DEFAULT;\n\n-- 删除主键约束\nALTER TABLE users2 DROP PRIMARY KEY;\n\n-- 删除唯一约束\nALTER TABLE users2 DROP INDEX username;\n\n-- 删除外键约束\nSHOW CREATE TABLE users2 --  CONSTRAINT `users2_ibfk_1` FOREIGN KEY (`pid`) REFERENCES `provinces` (`id`)\n\nALTER TABLE users2 DROP FOREIGN key users2_ibfk_1;\n```', '2019-02-11 12:21:15', '2019-02-11 12:21:15');
INSERT INTO `article` VALUES (3, 'mysql - column 的增删改查', '创建数据\n\n```sql\n-- DROP TABLE users; 创建过 users 表可以使用这个语句删除\n\n-- 创建表\nCREATE TABLE IF NOT EXISTS users(\n  id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n  username VARCHAR(20) NOT NULL,\n  password VARCHAR(32) NOT NULL,\n  age TINYINT UNSIGNED NOT NULL DEFAULT 10,\n  sex TINYINT\n);\n```\n\n<!--more-->\n\n## 插入数据\n\n- INSERT INTO 表名 VALUE\n- INSERT INTO 表名 set 列名 = xxx (可以进行子查询)\n- INSERT INTO 表名 SELECT ,,,,,,, (将查询结果插入指定的表中)\n\n```sql\n-- 插入数据\nINSERT users VALUES (NULL, \'TOM\', \'1234\', 22, 1); -- 一个列都不能漏\n\n-- 插入多条数据\nINSERT users VALUES (NULL, \'TOM\', \'1234\', 22, 1), (DEFAULT, \'Jhon\', \'4321\', DEFAULT, 1);\n\n-- set方法 与第一种方式的区别是，此方法可以使用子查询，但是一次性只能插入一条记录。\nINSERT users SET username=\'BEN\',password=\'569\'; --其余字段有默认值或者允许为空\n```\n\n## 更新数据\n\nUPDATA 表名 SET 字段名=值|表达式 WHERE 判断条件 (如省略 where 筛选,则更新所有记录)\n\n```sql\nUPDATE users SET age = age + 5;\n\nUPDATE users SET age=age-id,sex=0;\n\nUPDATE users SET age=age+10 where id % 2 =0;\n```\n\n## 删除数据\n\n```sql\nDELETE FROM tbl_name [WHERE where_condition]\n```\n\n## 查找记录\n\n```sql\n查找记录：SELECT select_expr [,select_expr ……]\n[FROM table_referrnces [WHERE where_condition]\n[GROUP BY{col_name | position} [ASC | DESCI],……]\n[HAVING where_condition]\n[ORDER BY {col_name |expr |position} [ASC | DESCI],……]\n\n-- demo\nSELECT id, username FROM users;\n\n-- 别名\nSELECT id AS userId, username AS name FROM users;\n```\n\n### 查询分组\n\n```sql\nSELECT sex FROM users GROUP BY sex  -- 数据库多条记录会被合并，譬如这里只有 1, null\n```\n\n- 分组条件 [HAVING where_condotion]\n\n`having` 后的条件必须为聚合函数或者出现在 `select` 所选择的字段中。\n\n```sql\nSELECT sex FROM users GROUP BY sex -- 对所有记录分组\n\n-- 报错 having 后的条件必须为聚合函数或者出现在 select 所选择的字段中。\nSELECT sex FROM users GROUP by sex  having age>35;\n\nSELECT sex FROM users GROUP BY sex HAVING count(id) > 2;\n```\n\n- 排序\n\n```sql\n-- 首先按照age升序排列（asc），其次按照id降序排列（desc）\nSELECT * FROM users ORDER BY age,id DESC;\n```\n\n- 限制返回的数据的数量\n\n1. select \\_ from users limit 2 意思为从取前两条记录。\n2. select \\_ from users limit 3,2 意思为从第 4 条记录开始取两条，而不是从第三条记录开始。\n3. ...\n\n### 子查询与链接\n\n```SQL\nuse test;\n\nCREATE TABLE IF NOT EXISTS tb4(\n  goodsId SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,\n  goodsName VARCHAR(20) NOT NULL,\n  price FLOAT UNSIGNED\n);\n\nINSERT tb4 VALUES (NULL, \'goods1\', 12.8), (NULL, \'goods2\', 95.8), (NULL, \'goods3\', 15.8), (NULL, \'goods2\', 50.5);\n\n-- 查找平均值\nselect avg(price) from tb4;\n\n-- 对平均值四舍五入\nselect round(avg(price), 2) from tb4; -- 保留两位小数\n\n-- 使用比较\nselect goodsId, goodsName, price from tb4 where price >= 20;\n\n-- 查找大于平均价格的\nselect goodsId, goodsName, price from tb4 where price >= (select round(avg(price), 2) from tb4);\n```\n| 运算符/关键字 |  ANY   |  SOME  |  ALL   |\n| :-----------: | :----: | :----: | :----: |\n|     >、>=     | 最小值 | 最小值 | 最大值 |\n|     <、<=     | 最大值 | 最大值 | 最小值 |\n|       =       | 任意值 | 任意值 |        |\n|    <>、!=     |        |        | 任意值 |\n\n使用[NOT]EXISTS 的子查询：如果子查询返回任何行，EXISTS 将返回 TURE；否则返回 FALSE.\n\n```sql\nselect * from tb4 where price >= all (select round(avg(price), 2) from tb4); -- 任意大于平均价格的记录\n```\n\n\n## 数据增删改查-demo（单表）\n\n### INSERT\n\n```sql\nCREATE TABLE IF NOT EXISTS goods (\n  id SMALLINT PRIMARY KEY AUTO_INCREMENT,\n  name VARCHAR(20) NOT NULL,\n  price FLOAT UNSIGNED,\n  origin VARCHAR(20) DEFAULT \'CHINA\'\n);\n\n\n-- 插入单条记录\nINSERT goods VALUES(NULL, \'oppo\', 5399, DEFAULT);\n\n-- 插入多条记录\nINSERT goods VALUES(NULL, \'iphone\', 8999, \'US\'), (NULL, \'meizu\', 6999, DEFAULT);\n\n-- SET 插入\nINSERT goods SET name=\'xiaomi\', price=999;\n\n-- SET 插入多条\nINSERT INTO goods (goodsName, price) VALUES(\'ss\',25),(\'bb\',125);\n```\n\n### SELECT\n\n```sql\nSELECT * FROM goods;\n\nSELECT goodsName, price FROM goods;\n\nSELECT goodsName, price FROM goods WHERE price > 1000; -- 加条件筛选\n```\n\n### UPDATE\n\n```sql\n-- 找到 iphone , 修改名字为 iphonX, 价格 9999\nUPDATE goods SET goodsName=\'iphonX\', price=9999 WHERE goodsName=\'iphone\';\n```\n\n### DELETE\n\n```SQL\nDELETE FROM goods WHERE goodsName=\'ss\'; -- DELETE FROM goods 删除所有\n```\n\n多表有 `left join` 、 `inner` 等等，这里不再讲述。', '2019-02-11 12:22:15', '2019-02-11 12:22:15');
INSERT INTO `article` VALUES (4, ' mysql - 聚合函数', '## 聚合函数（aggregation function）\n\n> 聚合函数（`aggregation function`）---也就是组函数，在一个行的集合（一组行）上进行操作，对每个组给一个结果。\n\n常用的组函数：\n\n| function                     | return       |\n| ---------------------------- | ------------ |\n| AVG([distinct] expr)         | 求平均值     |\n| COUNT({*  [distinct] } expr) | 统计行的数量 |\n| MAX([distinct] expr)         | 求最大值     |\n| MIN([distinct] expr)         | 求最小值     |\n| SUM([distinct] expr)         | 求累加和     |\n\n<!-- more -->\n\n1. 每个组函数接收一个参数\n2. 默认情况下，组函数忽略列值为null的行，不参与计算\n3. 有时，会使用关键字distinct剔除字段值重复的条数\n\n注意：\n\n- 当使用组函数的 `select` 语句中没有 `group by` 子句时，中间结果集中的所有行自动形成一组，然后计算组函数；\n- 组函数不允许嵌套，例如：`count(max(…))`；\n- 组函数的参数可以是列或是函数表达式；\n- 一个 `SELECT` 子句中可出现多个聚集函数。\n\n\n```bash\nmysql> select * from users;\n+----+----------+------+---------------------+---------------------+\n| id | name     | age  | createdAt           | updatedAt           |\n+----+----------+------+---------------------+---------------------+\n|  1 | guodada  |   18 | 2019-10-04 05:56:52 | 2019-10-04 05:56:52 |\n|  2 | guodada2 |   18 | 2019-10-04 05:57:01 | 2019-10-04 05:57:01 |\n|  3 | guodada3 | NULL | 2019-10-04 05:58:00 | 2019-10-04 05:58:00 |\n+----+----------+------+---------------------+---------------------+\n3 rows in set (0.00 sec)\n```\n\n## count 函数\n\n① `count(*)`：返回表中满足 `where` 条件的行的数量\n\n```sql\nSELECT COUNT(*) AS count FROM users WHERE age > 10 -- count 3\n```\n\n② `count(列)`：返回列值非空的行的数量\n\n```sql\nSELECT COUNT(age) AS count FROM users -- count 2\n```\n\n③ `count(distinct 列)`：返回列值非空的、并且列值不重复的行的数量\n\n```sql\nSELECT COUNT(distinct age) AS count FROM users -- count 1\n```\n\n④ `count(expr)`：根据表达式统计数据\n\n```sql\nSELECT COUNT(age=18) AS count FROM users; -- count 2\n```\n\n## max 和 min 函数---统计列中的最大最小值\n\n```sql\nSELECT MAX(age) as maxAge FROM users -- maxAge 18\nSELECT MIN(age) as minAge FROM users -- minAge 18\n```\n\n> 注意：如果统计的列中只有 `NULL` 值，那么 `MAX` 和 `MIN` 就返回 `NULL`\n\n## sum 和 avg 函数---求和与求平均\n\n！！表中列值为 `null` 的行不参与计算\n\n```sql\nSELECT AVG(age) as avgAge FROM users -- avgAge 18\nSELECT SUM(age) as snmAge FROM users -- sumAge 36\n```\n\n注意：要想列值为 `NULL` 的行也参与组函数的计算，必须使用 `IFNULL` 函数对 `NULL` 值做转换。\n\n## 分组聚合查询\n\n分组 SELECT 的基本格式：\n\n`select [聚合函数] 字段名 from 表名 [where 查询条件] [group by 字段名] [having 过滤条件]`\n\n```bash\nmysql> select name, count(*) as count from users where age > 10 group by name;\n+----------+-------+\n| name     | count |\n+----------+-------+\n| guodada  |     1 |\n| guodada2 |     1 |\n+----------+-------+\n2 rows in set (0.00 sec)\n```\n\n通过 `select` 在返回集字段中，这些字段要么就要包含在 `group by` 语句后面，作为分组的依据，要么就要被包含在聚合函数中。我们可以将 `group by` 操作想象成如下的一个过程：首先系统根据 `select` 语句得到一个结果集，然后根据分组字段，将具有相同分组字段的记录归并成了一条记录。这个时候剩下的那些不存在与 `group by` 语句后面作为分组依据的字段就很有可能出现多个值，但是目前一种分组情况只有一条记录，一个数据格是无法放入多个数值的，所以这个时候就需要通过一定的处理将这些多值的列转化成单值，然后将其放在对应的数据格中，那么完成这个步骤的就是前面讲到的聚合函数，这也就是为什么这些函数叫聚合函数了。\n\n- [MySQL最常用分组聚合函数](https://www.cnblogs.com/geaozhang/p/6745147.html#sum-avg)', '2019-02-11 12:24:26', '2019-02-11 12:24:26');
INSERT INTO `article` VALUES (5, 'react 入门', '```html\n<script src=\"https://unpkg.com/react@16/umd/react.development.js\"></script>\n<script src=\"https://unpkg.com/react-dom@16/umd/react-dom.development.js\"></script>\n<!-- 生产环境中不建议使用 -->\n<script src=\"https://unpkg.com/babel-standalone@6.15.0/babel.min.js\"></script>\n\n<div id=\"example\"></div>\n<script type=\"text/babel\">\nReactDOM.render(\n    <h1>Hello, world!</h1>,\n    document.getElementById(\'example\')\n);\n</script>\n```\n\n- react.min.js - React 的核心库\n- react-dom.min.js - 提供与 DOM 相关的功能\n- babel.min.js - Babel 可以将 ES6 代码转为 ES5 代码\n\n<!--more-->\n\n## 使用 create-react-app 快速构建 React 开发环境\n\n```\ncnpm install -g create-react-app\ncreate-react-app my-app\nnpm run eject\n```\n\n`TodoList`\n```jsx\nimport React, {Component} from \'react\';\n\nclass TodoList extends Component {\n    constructor(props) {\n        super(props)\n\n        this.state = {\n            list: [],\n            inputValue: \'\'\n        }\n\n        this.handleChange = this.handleChange.bind(this)\n        this.handleBtnClick = this.handleBtnClick.bind(this)\n    }\n\n    handleChange(e) {\n        this.setState({\n            inputValue: e.target.value\n        })\n    }\n\n    handleBtnClick() {\n        this.setState(\n            {\n                list: [...this.state.list, this.state.inputValue],\n                inputValue: \'\'\n            }\n        )\n    }\n\n    handleItemClick(index) {\n        let list = [...this.state.list]\n        list.splice(index, 1)\n        this.setState({\n            list\n        })\n    }\n\n    render() {\n        return (\n            <div>\n                <div>\n                    <input value={this.state.inputValue} onChange={this.handleChange}/>\n                    <button onClick={this.handleBtnClick} className=\'btn\'>add</button>\n                </div>\n                <ul>\n                    {\n                        this.state.list.map((item, index) => {\n                            return (\n                                <li key={index} onClick={this.handleItemClick.bind(this, index)}>{item}</li>\n                            )\n                        })\n                    }\n                </ul>\n            </div>\n        )\n    }\n}\n\nexport default TodoList\n```\n\n组件化 \n`todoList`\n\n```js\nimport React, {Component} from \'react\';\nimport TodoItem from \'./TodoItem\'\n\nclass TodoList extends Component {\n    constructor(props) {\n        super(props)\n\n        this.state = {\n            list: [],\n            inputValue: \'\'\n        }\n\n        this.handleChange = this.handleChange.bind(this)\n        this.handleBtnClick = this.handleBtnClick.bind(this)\n    }\n\n    handleChange(e) {\n        this.setState({\n            inputValue: e.target.value\n        })\n    }\n\n    handleBtnClick() {\n        this.setState(\n            {\n                list: [...this.state.list, this.state.inputValue],\n                inputValue: \'\'\n            }\n        )\n    }\n\n    handleItemClick(index) {\n        let list = [...this.state.list]\n        list.splice(index, 1)\n        this.setState({list})\n    }\n\n    render() {\n        return (\n            <div>\n                <div>\n                    <input value={this.state.inputValue} onChange={this.handleChange}/>\n                    <button onClick={this.handleBtnClick} className=\'btn\'>add</button>\n                </div>\n                <ul>\n                    {\n                        this.state.list.map((item, index) => {\n                            // return (\n                            //     <li key={index} onClick={this.handleItemClick.bind(this, index)}>{item}</li>\n                            // )\n                            return (\n                                <TodoItem\n                                    key={index}\n                                    content={item}\n                                    index={index}\n                                    delete={this.handleItemClick.bind(this, index)}\n                                />\n                            )\n                        })\n                    }\n                </ul>\n            </div>\n        )\n    }\n}\n\nexport default TodoList\n```\n`todoItem`\n``` jsx\nimport React, {Component} from \'react\';\n class TodoItem extends Component {\n     constructor(props) {\n        super(props)\n        this.handleDelete = this.handleDelete.bind(this)\n    }\n     // 子组件想要和父组件通信，要调用父组件传递过来的方法\n     handleDelete(index) {\n        this.props.delete(index)\n    }\n     // 父组件通过属性的形式向子组件传递参数\n    // 子组件通过props接受父组件传递过来的参数\n     render() {\n        return (\n            <li onClick={this.handleDelete}>{this.props.content}</li>\n        )\n    }\n}\n export default TodoItem\n```', '2019-02-11 12:25:21', '2019-02-23 04:27:26');
INSERT INTO `article` VALUES (6, 'react-context 实现 todoList', '`src/context/TodoContext.js`\n\n```jsx\nimport React, { Component } from \'react\'\n\nexport const TodoContext = React.createContext()\n\nexport class TodoProvider extends Component {\n  state = {\n    todoList: []\n  }\n\n  addTodo = text => {\n    const todoList = [...this.state.todoList, { id: Math.random(), text }]\n    this.setState({ todoList })\n  }\n\n  deleteTodo = id => {\n    const todoList = this.state.todoList.filter(todo => todo.id !== id)\n    this.setState({ todoList })\n  }\n\n  clearTodos = () => {\n    this.setState({ todoList: [] })\n  }\n\n  render() {\n    return (\n      <TodoContext.Provider\n        value={{\n          todoList: this.state.todoList,\n          addTodo: this.addTodo,\n          deleteTodo: this.deleteTodo,\n          clearTodos: this.clearTodos\n        }}>\n        {this.props.children}\n      </TodoContext.Provider>\n    )\n  }\n}\n```\n<!--more-->\n\n`App.jsx`\n\n```jsx\nimport React, { Component } from \'react\'\n\nimport { TodoContext, TodoProvider } from \'./context/TodoContext\'\n\nclass App extends Component {\n  state = {\n    text: \'\'\n  }\n\n  render() {\n    const { todoList, addTodo, deleteTodo, clearTodos } = this.props\n    const { text } = this.state\n    return (\n      <div>\n        <h2>TodoList</h2>\n        <div>\n          <input\n            type=\"text\"\n            placeholder=\"请输入内容\"\n            onChange={e => this.setState({ text: e.target.value })}\n          />\n          <button onClick={e => addTodo(text)}>AddTodo</button>\n          <button onClick={clearTodos}>clearTodos</button>\n        </div>\n        <ul>\n          {todoList.map(todo => (\n            <li key={todo.id}>\n              {todo.text}\n              <button onClick={e => deleteTodo(todo.id)}>delete</button>\n            </li>\n          ))}\n        </ul>\n      </div>\n    )\n  }\n}\n\nconst Hoc = WrappedComponent =>\n  class extends Component {\n    render() {\n      return (\n        <TodoProvider>\n          <TodoContext.Consumer>\n            {ctx => <WrappedComponent {...ctx} />}\n          </TodoContext.Consumer>\n        </TodoProvider>\n      )\n    }\n  }\n\nexport default Hoc(App)\n```', '2019-02-11 12:26:32', '2019-02-23 04:27:13');
INSERT INTO `article` VALUES (7, 'react-context', '## 简单使用\n\n`Context` 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据，你可以看做为 `redux`，因为 `redux` 也是通过这个东东实现的。\n\n```jsx\nimport React, { Component } from \'react\'\n\n/**\n * 1. 创建 context\n * 2. 根组件 App 包裹 MyContext.Provider\n * 3. App => Father => Child => MyContext.Consumer => context.age 取出结果\n */\nconst MyContext = React.createContext()\n\nconst Child = () => (\n  <MyContext.Consumer>{({ age }) => <p>My age is {age}</p>}</MyContext.Consumer>\n)\n\nconst Father = () => <Child />\n\nclass App extends Component {\n  render() {\n    return (\n      <MyContext.Provider value={{ age: 22 }}>\n        <Father />\n      </MyContext.Provider>\n    )\n  }\n}\n\nexport default App\n```\n<!--more-->\n\n## 理论知识\n\n### React.createContext\n\n```jsx\nconst { Provider, Consumer } = React.createContext(defaultValue)\n```\n\n创建一对 { `Provider`, `Consumer` }。当 React 渲染 `context` 组件 `Consumer` 时，它将从组件树的上层中最接近的匹配的 `Provider` 读取当前的 `context` 值。\n\n### Provider\n\n```jsx\n<Provider value={/* some value */}>\n\n```\n\nReact 组件允许 `Consumers` 订阅 `context` 的改变。\n接收一个 `value` 属性传递给 `Provider` 的后代 `Consumers`。一个 `Provider` 可以联系到多个 `Consumers`。Providers 可以被嵌套以覆盖组件树内更深层次的值。\n\n### Consumer\n\n```jsx\n<Consumer>\n  {value => /* render something based on the context value */}\n</Consumer>\n```\n\n一个可以订阅 `context` 变化的 React 组件。\n\n注意，`MyContext.Consumer` 使用的是 `render props` 这种模式，`render props` 模式指的是让 `prop` 可以是一个 `render` 函数\n\n## 父子耦合\n\n经常需要从组件树中某个深度嵌套的组件中更新 `context`。在这种情况下，可以通过 `context` 向下传递一个函数，以允许 `Consumer` 更新 `context` ：\n\n```jsx\nimport React, { Component } from \'react\'\n\nconst MyContext = React.createContext()\n\nconst Child = () => (\n  <MyContext.Consumer>\n     {ctx => (\n      <div>\n        <p>My age is {ctx.age}</p>\n        <button onClick={ctx.changeAge}>changeAge</button>\n      </div>\n    )}\n  </MyContext.Consumer>\n)\n\nconst Father = () => <Child />\n\nclass App extends Component {\n  state = {\n    age: 22\n  }\n  \n  changeAge = () => {\n    this.setState(prevState => ({\n      age: ++prevState.age\n    }))\n  }\n\n  render() {\n    return (\n      <MyContext.Provider value={{\n        age: this.state.age,\n        changeAge: this.changeAge\n      }}>\n        <Father />\n      </MyContext.Provider>\n    )\n  }\n}\n\nexport default App\n```\n\n## 作用于多个上下文\n\n为了保持 `context` 快速进行二次渲染， `React` 需要使每一个 `Consumer` 在组件树中成为一个单独的节点。\n\n```jsx\nimport React, { Component } from \'react\'\n\nconst MyContext = React.createContext()\n\nconst UserContext = React.createContext()\n\nconst Child = () => (\n  <MyContext.Consumer>\n    {ctx => (\n      <UserContext.Consumer>\n        {user => (\n          <div>\n            <p>My name is {user.name}</p>\n            <p>My age is {ctx.age}</p>\n            <button onClick={ctx.changeAge}>changeAge</button>\n          </div>\n        )}\n      </UserContext.Consumer>\n    )}\n  </MyContext.Consumer>\n)\n\nconst Father = () => <Child />\n\nclass App extends Component {\n  state = {\n    age: 22,\n    name: \'郭大大\'\n  }\n\n  changeAge = () => {\n    this.setState(prevState => ({\n      age: ++prevState.age\n    }))\n  }\n\n  render() {\n    return (\n      <MyContext.Provider\n        value={{\n          age: this.state.age,\n          changeAge: this.changeAge\n        }}>\n        <UserContext.Provider value={{ name: this.state.name }}>\n          <Father />\n        </UserContext.Provider>\n      </MyContext.Provider>\n    )\n  }\n}\n\nexport default App\n```\n\n如果两个或者多个上下文的值经常被一起使用，也许你需要考虑你自己渲染属性的组件提供给它们。\n\n\n## 在生命周期方法中访问 Context\n\n在生命周期方法中从上下文访问值是一种相对常见的用例。而不是将上下文添加到每个生命周期方法中，只需要将它作为一个 `props` 传递，然后像通常使用 `props` 一样去使用它。\n\n```jsx\nimport React, { Component } from \'react\'\n\nconst MyContext = React.createContext()\n\nclass Child extends Component {\n  \n  componentDidMount() {\n   console.log(this.props.ctx) \n  }  \n\n  render() {\n    const { age, changeAge } = this.props.ctx\n    return (\n      <div>\n        <p>My age is {age}</p>\n        <button onClick={changeAge}>changeAge</button>\n      </div>\n    )\n  }\n}\n\n\nconst Father = props => (\n  <MyContext.Consumer>\n    {ctx => <Child {...props} ctx={ctx} />}\n  </MyContext.Consumer>\n)\n\nclass App extends Component {\n  state = {\n    age: 22\n  }\n\n  changeAge = () => {\n    this.setState(prevState => ({\n      age: ++prevState.age\n    }))\n  }\n\n  render() {\n    return (\n      <MyContext.Provider\n        value={{\n          age: this.state.age,\n          changeAge: this.changeAge\n        }}>\n        <Father />\n      </MyContext.Provider>\n    )\n  }\n}\n\nexport default App\n```\n\n## 转发 Refs\n\n一个关于渲染属性API的问题是 `refs` 不会自动的传递给被封装的元素。为了解决这个问题，使用 `React.forwardRef`：\n\n- fancy-button.js\n\n```jsx\nclass FancyButton extends React.Component {\n  focus() {\n    // ...\n  }\n\n  // ...\n}\n\n// 使用 context 传递当前的 \"theme\" 给 FancyButton.\n// 使用 forwardRef 传递 refs 给 FancyButton 也是可以的.\nexport default React.forwardRef((props, ref) => (\n  <ThemeContext.Consumer>\n    {theme => (\n      <FancyButton {...props} theme={theme} ref={ref} />\n    )}\n  </ThemeContext.Consumer>\n))\n```\n- app.js\n\n```jsx\nimport FancyButton from \'./fancy-button\'\n\nconst ref = React.createRef()\n\n// ref属性将指向 FancyButton 组件,\n// ThemeContext.Consumer 没有包裹它\n// 这意味着我们可以调用 FancyButton 的方法就像这样 ref.current.focus()\n<FancyButton ref={ref} onClick={handleClick}>\n  Click me!\n</FancyButton>\n```\n\n## 尽量减少使用 context\n\n因为 `context` 使用 `reference identity` 确定何时重新渲染，在 `Consumer` 中，当一个 `Provider` 的父节点重新渲染的时候，有一些问题可能触发意外的渲染。例如下面的代码，所有的 `Consumner` 在 `Provider` 重新渲染之时，每次都将重新渲染，因为一个新的对象总是被创建对应 `Provider` 里的 value\n\n```jsx\nclass App extends React.Component {\n  render() {\n    return (\n      <Provider value={{something: \'something\'}}>\n        <Toolbar />\n      </Provider>\n    );\n  }\n}\n```\n\n为了防止这样, 提升 `value` 到父节点的 `state` 里:\n\n```jsx\nclass App extends React.Component {\n  constructor(props) {\n    this.state = {\n      value: {something: \'something\'},\n    };\n  }\n\n  render() {\n    return (\n      <Provider value={this.state.value}>\n        <Toolbar />\n      </Provider>\n    );\n  }\n}\n```\n\n## 注意点\n\nReact context的局限性：\n\n1. 在组件树中，如果中间某一个组件 ShouldComponentUpdate returning false 了，会阻碍 context 的正常传值，导致子组件无法获取更新。\n2. 组件本身 extends React.PureComponent 也会阻碍 context 的更新。\n3. Context 应该是唯一不可变的\n4. 组件只在初始化的时候去获取 Context\n', '2019-02-11 12:26:54', '2019-02-23 04:27:03');
INSERT INTO `article` VALUES (8, 'react - 高阶组件', '## 高阶函数 / 柯理化\n\n> 高阶函数（`Higher Order Function`）=> 参数或返回值为函数\n\n比较常见的有数组的遍历方式 Map、Reduce、Filter、Sort；常用的 redux 中的 `connect` 方法也是高阶函数。\n\n```js\n// 高阶函数 - 简单的例子\nfunction add(a, b, fn) {\n  return fn(a) + fn(b)\n}\nvar fn = function(a) {\n  return a * a\n}\nadd(2, 3, fn) // 13\n```\n\n> 函数柯里化 接受多个参数的函数变换成接受一个单一参数（最初函数的第一个参数）的函数，并且返回接受余下的参数而且返回结果的新函数的技术\n\n```js\nconst add = function(x) {\n  return function(y) {\n    return function(z) {\n      return x + y + z\n    }\n  }\n}\n\nconsole.log(add(1)(2)(3)) // 6\nconsole.log(add(1)) // function (y) {...}\n```\n\n## 高阶组件（属性代理）\n\n> HOC(全称 `Higher-order component`)是一种 React 的进阶使用方法，主要还是为了便于组件的复用。HOC 就是一个方法，获取一个组件，返回一个更高级的组件。\n\n以下是简单实现的 hoc:\n\n```js\nconst Hoc = WrappedComponent =>\n  class extends Component {\n    render() {\n      return <WrappedComponent {...this.props} name={\'guodada\'} />\n    }\n  }\n\n// use\nconst Demo = props => <span>{props.name}</span>\nexport default Hoc(Demo)\n```\n\n### hoc 的具体用途\n\n1. 代码复用\n2. 对 props 进行增删改、监控\n3. 渲染劫持\n\n其实，除了代码复用和模块化，`HOC` 做的其实就是劫持，由于传入的 `wrappedComponent` 是作为一个 `child` 进行渲染的，上级传入的 props 都是直接传给 `HOC` 的，所以 `HOC` 组件拥有很大的权限去修改 `props` 和控制渲染。\n\n对 props 进行增加上式代码用例已经说明了，我们可以使用 `HOC` 的特性来做一些渲染劫持的事情。譬如，控制组件的渲染：\n\n```js\n// 在传入的 data 未加载完时，显示 loading...\nconst Hoc = WrappedComponent =>\n  class extends Component {\n    render() {\n      if (!this.props.data) return <div>Loading...</div>\n      return <WrappedComponent {...this.props} />\n    }\n  }\n```\n\n又或者我们可以对 props 进行监控\n\n```jsx\nconst Hoc = WrappedComponent =>\n  class extends Component {\n    componentDidUpdate(prevProps, prevState) {\n      console.log(\'prevProps\', prevProps)\n      console.log(\'nextProps\', this.props)\n    }\n    render() {\n      return <WrappedComponent {...this.props} />\n    }\n  }\n```\n\n除此外还可以用来做页面权限管理。可以通过 HOC 对组件进行包裹，当跳转到当前页面的时候，检查用户是否含有对应的权限。如果有的话，渲染页面。如果没有的话，跳转到其他页面(比如无权限页面，或者登陆页面)。\n\n### 使用 HOC 注意项\n\n1. ref 不能获取到你想要获取的 ref\n\n```js\nconst Hoc = WrappedComponent =>\n  class extends Component {\n    state = { name: \'hoc\' }\n    render() {\n      return <WrappedComponent {...this.props} />\n    }\n  }\n\nclass Demo extends Component {\n  state = { name: \'guodada\' }\n  render() {\n    return null\n  }\n}\n\nconst Test = Hoc(Demo)\n\nclass App extends Component {\n  componentDidMount() {\n    console.log(this.demoRef.state) // { name: \'hoc\' }, this.demoRef 不是 Demo 组件的实例 而是 hoc 的实例对象\n  }\n\n  render() {\n    return <Test ref={node => (this.demoRef = node)} />\n  }\n}\n```\n\n2. `Component` 上面绑定的 `Static` 方法会丢失\n\n```jsx\n// 定义一个static方法\nWrappedComponent.staticMethod = function() {\n  /*...*/\n}\n// 利用HOC包裹\nconst EnhancedComponent = enhance(WrappedComponent)\n\n// 返回的方法无法获取到staticMethod\ntypeof EnhancedComponent.staticMethod === \'undefined\' // true\n```\n\n这里有一个解决方法，就是 `hoist-non-react-statics` 组件，这个组件会自动把所有绑定在对象上的非 `React` 方法都绑定到新的对象上：\n\n### 做点小运用\n\n使用 HOC 来代理用户的一些表单行为，如下：\n\n```jsx\nimport React, { Component } from \'react\'\n\nconst Form = WrappedComponent =>\n  class extends Component {\n    state = { fields: {} }\n\n    onChange = key => e => {\n      let { fields } = this.state\n      fields[key] = e.target.value\n      this.setState({ fields })\n    }\n\n    getField = fieldName => ({\n      onChange: this.onChange(fieldName)\n    })\n\n    submit = () => {\n      console.log(this.state.fields)\n    }\n\n    render() {\n      const props = {\n        ...this.props,\n        onChange: this.onChange,\n        getField: this.getField,\n        submit: this.submit\n      }\n      return <WrappedComponent {...props} />\n    }\n  }\n\nclass App extends Component {\n  render() {\n    return (\n      <div>\n        <input type=\"text\" {...this.props.getField(\'name\')} />\n        <button onClick={this.props.submit}>submit</button>\n      </div>\n    )\n  }\n}\n\nexport default Form(App)\n```\n\n## 高阶组件（反继承）\n\n反向继承(`Inheritance Inversion`)，简称 `II`，跟属性代理的方式不同的是，`II` 采用通过 去继承 `WrappedComponent`，本来是一种嵌套的关系，结果 `II` 返回的组件却继承了 `WrappedComponent`，这看起来是一种反转的关系。\n\n简单来理解，代理模式来自上下级组件的包装，而反继承模式的 HOC 和被包装的组件属于同级组件，可以互相调用彼此的方法和 state。而且可以劫持生命周期。\n\n使用了该模式之后两者可以视为同一组件去使用，譬如 `II` 可以调用包装的组件的方法和属性，组件可以调用 `II` 的方法和属性：\n\n```js\nimport React, { Component } from \'react\'\n\nconst Hoc = WrappedComponent =>\n  class extends WrappedComponent {\n    constructor(props) {\n      super(props)\n      this.state = {\n        theme: \'green\', // 公用属性\n        ...this.state // 组件内的属性可以覆盖 hoc 定义的属性\n      }\n    }\n\n    componentDidMount() {\n      console.log(\'run hoc componentDidMount\') // run hoc componentDidMount\n    }\n\n    toggle = () => {\n      this.setState(\n        prevState => ({\n          theme: prevState.theme === \'green\' ? \'red\' : \'green\'\n        }),\n        () => {\n          this.log(this.state.theme)\n        }\n      )\n    }\n\n    render() {\n      return super.render()\n    }\n  }\n\nclass App extends Component {\n  componentDidMount() {\n    console.log(\'run App componentDidMount\') // 不执行，因为被 hoc 劫持了\n  }\n\n  log = theme => {\n    console.log(theme)\n  }\n\n  render() {\n    return (\n      <div>\n        <div>{this.state.theme}</div>\n        <button onClick={this.toggle}>click</button>\n      </div>\n    )\n  }\n}\n\nexport default Hoc(App)\n```\n', '2019-02-11 12:27:18', '2019-02-23 04:26:52');
INSERT INTO `article` VALUES (9, 'react - hooks(v16.7)', '## 前言\n\n本文不做概念性的解析，旨在实操 `hooks`，相关资源可以自行谷歌。以下提供相关参考资料：\n\n- [Introducing Hooks](https://reactjs.org/docs/hooks-intro.html)\n- [理解 React Hooks](https://juejin.im/post/5be409696fb9a049b13db042)\n- [React Hooks 实用指南](https://juejin.im/post/5bffc271e51d454dca3547b1#heading-0) - 大都借鉴这篇文章\n- [Hooks 一览](https://juejin.im/post/5bd53d6a51882528382d8108)\n\n<!--more-->\n\n## useState\n\n> `useState` 可以让您的函数组件也具备类组件的 `state` 功能。\n\n```js\n/**\n * @state - state的值\n * @setState - 更新state的函数, 接受一个参数值来更新 state\n */\nconst [state, setState] = useState(initialState)\n```\n\n### 案例\n\n```js\nimport React, { useState } from \'react\'\n\nfunction Base() {\n  const [count, setCount] = useState(0)\n  return (\n    <div>\n      <p>You clicked {count} times</p>\n      <button onClick={() => setCount(count + 1)}>Click me</button>\n    </div>\n  )\n}\n\nexport default Base\n```\n\n值得注意的是，`state` 是对象的话， `setState` 接收什么参数，就更新对象下的所有属性，而不是更新单个属性。\n\n```js\nimport React, { useState } from \'react\'\n\nfunction Demo2() {\n  const [info, setInfo] = useState({\n    name: \'guodada\',\n    age: 22\n  })\n\n  return (\n    <div>\n      <p>name: {info.name}</p>\n      <p>age: {info.age}</p>\n      <button onClick={() => setInfo({ name: \'Sam\' })}>setInfo</button>\n    </div>\n  )\n}\n```\n\n`click button` => `info = { name: \'Sam\' }`，`age` 丢失。\n\n根据业务需求，我们可以在函数组件中使用多个 `useState`，这里不再进行演示。\n\n## useEffect\n\n`Effect Hook`: 它与 `React Class` 中的 `componentDidMount`，`componentDidUpdate` 和 `componentWillUnmount` 具有相同的用途。模拟的是生命周期\n\n```js\n/**\n * @didUpdate - function\n * @[] - 参数2为数组，不加参数或者不写的话任何state 的变化都会执行 didUpdate 函数\n */\nuseEffect(didUpdate, [])\n```\n\n### 案例\n\n```js\nclass Example extends React.Component {\n  state = { count: 0 }\n\n  componentDidMount() {\n    document.title = `You clicked ${this.state.count} times`\n  }\n\n  componentDidUpdate() {\n    document.title = `You clicked ${this.state.count} times`\n  }\n\n  render() {\n    return (\n      <div>\n        <p>You clicked {this.state.count} times</p>\n        <button onClick={() => this.setState({ count: this.state.count + 1 })}>\n          Click me\n        </button>\n      </div>\n    )\n  }\n}\n```\n\n等同于\n\n```js\nimport { useState, useEffect } from \'react\'\n\nfunction Example() {\n  const [count, setCount] = useState(0)\n\n  useEffect(() => {\n    document.title = `You clicked ${count} times`\n  })\n\n  return (\n    <div>\n      <p>You clicked {count} times</p>\n      <button onClick={() => setCount(count + 1)}>Click me</button>\n    </div>\n  )\n}\n```\n\n- 添加第二个参数进行控制\n```js\nimport React, { useState, useEffect } from \'react\'\n\nfunction Example() {\n  const [count, setCount] = useState(0)\n  const [count2, setCount2] = useState(0)\n\n  useEffect(() => {\n    console.log(\'run useEffect\')\n  }, [count])  // 只有count 变化时才执行这个 useEffect 函数\n\n  return (\n    <div>\n      <p>You clicked {count} times</p>\n      <button onClick={() => setCount(count + 1)}>Click me</button>\n      <p>You clicked {count2} times</p>\n      <button onClick={() => setCount2(count2 + 1)}>Click me</button>\n    </div>\n  )\n}\n```\n\n## useContext\n\n> `useReducer` 是 `useState` 的代提方案。当你有一些更负责的数据时可以使用它。（组件本地的redux）\n\n使用语法如下：\n\n```js\n/**\n * @state => your state\n * @dispatch\n *  @param {state} \n *  @param {action}  \n **/\nconst [state, dispatch] = useReducer(reducer, initialState)\n```\n\n### 案例\n\n```js\nimport React, { Component, useReducer } from \'react\'\n\nfunction TestUseReducer() {\n  const [state, dispatch] = useReducer(\n    (state, action) => {\n      switch (action.type) {\n        case \'update\':\n          return { name: action.payload }\n        default:\n          return state\n      }\n    },\n    { name: \'\' }\n  )\n\n  const handleNameChange = e => {\n    dispatch({ type: \'update\', payload: e.target.value })\n  }\n\n  return (\n    <div>\n      <p>你好：{state.name}</p>\n      <input onChange={handleNameChange} />\n    </div>\n  )\n}\n\nclass App extends Component {\n  render() {\n    return (\n      <div className=\"App\">\n        <h1>Hello</h1>\n        <h2>Start editing to see some magic happen!</h2>\n        <TestUseReducer />\n      </div>\n    )\n  }\n}\n\nexport default App\n```\n\n## useCallback\n\n> `useCallback` 和 `useMemo` 有些相似。它接收一个内联函数和一个数组，它返回的是一个记忆化版本的函数。\n\n使用语法如下：\n\n```jsx\nconst memoizedValue = useMemo(() => computeExpensiveValue(a), [a])\n```\n\n### 案例\n\n```jsx\nimport React, { Component, useCallback } from \'react\'\n\nfunction TestUseCallback({ num }) {\n  const memoizedCallback = useCallback(\n    () => {\n      console.log(\'这里监听 num 值的更新重新做一些操作和计算\')\n      num.forEach(item => item++ )\n      return num\n    },\n    [num]\n  )\n  console.log(\'记忆 num > \', memoizedCallback())\n  console.log(\'原始 num > \', num)\n  return null\n}\n\nconst num1 = [1, 2, 3]\nconst num2 = [4, 5, 6]\n\nclass App extends Component {\n  state = { num: num1, count: 0 }\n\n  componentDidMount() {\n    setInterval(() => {\n      this.setState(state => ({\n        count: state.count + 1\n      }))\n    }, 3000)\n  }\n\n  handleChangeNum = () => {\n    this.setState({ num: num2 })\n  }\n\n  render() {\n    const { num } = this.state\n\n    return (\n      <div className=\"App\">\n        <h1>Hello</h1>\n        <h2>Start editing to see some magic happen!</h2>\n        <button onClick={this.handleChangeNum}>修改传入的Num值</button>\n        <TestUseCallback num={num} />\n      </div>\n    )\n  }\n}\n\nexport default App\n```\n\n## useRef\n\n```js\nimport React, { useRef } from \'react\'\n\nfunction TestUseRef() {\n  const inputEl = useRef(null)\n  \n  const onButtonClick = () => {\n    inputEl.current.focus() // 设置useRef返回对象的值\n  }\n\n  return (\n    <div>\n      <p>TestUseRef</p>\n      <div>\n        <input ref={inputEl} type=\"text\" />\n        <button onClick={onButtonClick}>input聚焦</button>\n      </div>\n    </div>\n  )\n}\n\nexport default TestUseRef\n```', '2019-02-11 12:27:48', '2019-02-23 04:26:30');
INSERT INTO `article` VALUES (10, 'react - lazy(v16.6)', '## 动态 import\n\n在 [Code-Splitting](https://reactjs.org/docs/code-splitting.html#import) 部分，提出拆分组件的最佳方式（best way） 是使用动态的 import 方式。\n\n比如下面两种使用方式的对比：\n\n```js\n// 之前\nimport { add } from \'./math\'\n\nconsole.log(add(16, 26))\n\n// 之后\nimport(\'./math\').then(math => {\n  console.log(math.add(16, 26))\n})\n```\n\n可以发现动态 `import` 提供了 `Promise` 规范的 API，比如 `.then()`\n\n<!--more-->\n\n## demo\n\n动态 `import` 主要应用场景是延迟加载方法，对于组件来说，并不是很适用，但是 `React.lazy` 对于组件的加载则是有比较大的帮助。\n\n> `React.lazy` 和 `suspense` 并不适用于服务端渲染\n\n```jsx\nimport React, { Component, lazy, Suspense } from \'react\'\n\nconst MyComponent = lazy(() => import(\'./MyComponent\'))\n\nclass App extends Component {\n  render() {\n    // lazy 需要配合 Suspense 使用\n    // Suspense 使用的时候，fallback 一定是存在且有内容的， 否则会报错。\n    return (\n      <Suspense fallback={<div>Loading...</div>}>\n        <MyComponent />\n      </Suspense>\n    )\n  }\n}\nexport default App\n```\n\n## 实现 lazy-load\n\n```jsx\nimport React from \'react\'\nimport NProgress from \'nprogress\'\n\nexport default loadComponent =>\n  class AsyncComponent extends React.Component {\n    state = { Component: null }\n\n    async componentDidMount() {\n      if (this.state.Component !== null) return\n      NProgress.start()\n      try {\n        const { default: Component } = await loadComponent()\n        this.setState({ Component })\n      } catch (err) {\n        console.error(`Cannot load component in <AsyncComponent />`)\n        throw err\n      }\n      NProgress.done()\n    }\n\n    render() {\n      const { Component } = this.state\n      return Component ? <Component {...this.props} /> : null\n    }\n  }\n```\n\n## react-loadable （router4 推荐）\n\n[react-router - [译] Code Splitting](https://gershonv.github.io/2018/11/07/react-router-3/)\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Route, Switch } from \'react-router-dom\'\nimport Loadable from \'react-loadable\'\n\nconst NoFound = Loadable({\n  loader: () => import(\'./components/NoFound\'),\n  loading: <div>loading</div>\n})\n\nclass App extends Component {\n  render() {\n    return (\n      <Router>\n        <Switch>\n          <Route exact path=\"/\" component={() => <h2>Home</h2>} />\n          <Route component={NoFound} />\n        </Switch>\n      </Router>\n    )\n  }\n}\nexport default App\n```', '2019-02-11 12:28:14', '2019-02-23 04:26:08');
INSERT INTO `article` VALUES (11, 'react - 生命周期', '## v16.3+\n\n- Mounting\n  - constructor(props)\n  - static getDerivedStateFromProps(props, state)\n  - render()\n  - componentDidMount()\n- Updating\n  - static getDerivedStateFromProps()\n  - shouldComponentUpdate(nextProps, nextState)\n  - render()\n  - getSnapshotBeforeUpdate(prevProps, prevState)\n  - componentDidUpdate(prevProps, prevState, snapshot)\n\n<!--more-->\n\n### constructor(props)\n\nReact 组件的构造函数在安装之前被调用。在为 React.Component 子类实现构造函数时，应该在任何其他语句之前调用 `super(props)`。\n否则，`this.props` 将在构造函数中未定义，这可能导致错误。\n\nAvoid copying props into state! This is a common mistake:\n\n```js\nconstructor(props) {\n super(props)\n // Don\'t do this!\n this.state = { color: props.color }\n}\n```\n\n### static getDerivedStateFromProps(nextProps, prevState)\n\n`props / state` 改变时触发，需要返回一个对象或者 `null`，相当于 `setState`\n\n- demo\n\n```js\nstatic getDerivedStateFromProps(nextProps, prevState){\n  if (nextProps.sum !== prevState.sum) return { sum: nextProps.sum } // 类似于 setState({ sum: nextProps.sum })\n  return null\n}\n```\n\n### render()\n\n```js\nrender(){\n  // don\'t do this\n  this.setState({ num: 12 })\n  return null\n}\n\n```\n\n### componentDidMount()\n\n组件挂载后。\n\n### shouldComponentUpdate(nextProps, nextState)\n\n```js\nshouldComponentUpdate(nextProps, nextState)\n```\n\nreturn true / false 来决定是否重新 render\n\n### getSnapshotBeforeUpdate(prevProps, prevState)\n\n相当于 `componentWillUpdate`\n\n### componentDidUpdate(prevProps, prevState, snapshot)\n\n更新后 - 这里谨慎使用 setState()\n\n## v16.3 以下\n\n```jsx\nimport React, { Component } from \'react\'\n\n/**\n *\n * 挂载数据：\n * @example constructor => componentWillMount => render => componentDidMount\n *\n * 数据变化：\n * @example props change: componentWillReceiveProps => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate\n * @example state change: shouldComponentUpdate => componentWillUpdate => componentDidUpdate\n *\n */\nclass LifeCycle extends React.Component {\n  constructor() {\n    super() // 声明constructor时必须调用super方法\n    this.state = {\n      subNum: 2\n    }\n    console.log(\'01 constructor\')\n  }\n\n  componentWillMount() {\n    console.log(\'02 componentWillMount\')\n  }\n\n  componentDidMount() {\n    console.log(\'04 componentDidMount\')\n  }\n\n  componentWillReceiveProps(nextProps) {\n    console.log(\'05 componentWillReceiveProps\')\n  }\n\n  shouldComponentUpdate(nextProps, nextState) {\n    console.log(\'06 shouldComponentUpdate\')\n    return true // 记得要返回true\n  }\n\n  componentWillUpdate(nextProps, nextState) {\n    console.log(\'07 componentWillUpdate\')\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    console.log(\'08 componentDidUpdate\')\n  }\n\n  componentWillUnmount() {\n    console.log(\'09 componentWillUnmount\')\n  }\n\n  changeState = () => {\n    this.setState(prevState => ({\n      subNum: ++prevState.subNum\n    }))\n  }\n\n  render() {\n    return (\n      <div>\n        <button onClick={this.changeState}>change state</button>\n        <h2>{this.state.subNum}</h2>\n      </div>\n    )\n  }\n}\n\nclass App extends Component {\n  state = {\n    num: 1\n  }\n\n  changeProps = () => {\n    // this.setState((prevState, props) => ({}))\n    this.setState(prevState => ({\n      num: ++prevState.num\n    }))\n  }\n\n  render() {\n    return (\n      <div>\n        <button onClick={this.changeProps}>change props</button>\n        <hr />\n        <LifeCycle num={this.state.num} />\n      </div>\n    )\n  }\n}\n\nexport default App\n```\n', '2019-02-11 12:28:32', '2019-02-23 04:25:54');
INSERT INTO `article` VALUES (12, 'react - Portals', '`Portals` 指定挂载组件到某个节点，适用于 `modal`、`toolTip`...\n\n我们不希望 `modal` 组件的节点出现在 `root` 根节点中。。。。\n\n```js\nReactDOM.createPortal(child, container)\n```\n\n- `child` : The first argument (child) is any renderable React child，such as an element, string, or fragment\n  即可渲染的 react 组件\n- `container` : a DOM element\n\n<!--more-->\n\n## 用法\n\n通常，如果你的组件的 render 方法返回一个元素时，它作为最接近的父节点的子节点挂载到 DOM 中：\n\n```jsx\nrender() {\n  // React mounts a new div and renders the children into it\n  return (\n    <div>\n      {this.props.children}\n    </div>\n  )\n}\n```\n\n但是，有时候要把子节点插入 DOM 中的不同位置时，是有用的：\n\n```jsx\nrender() {\n  // React does *not* create a new div. It renders the children into `domNode`.\n  // `domNode` is any valid DOM node, regardless of its location in the DOM.\n  return ReactDOM.createPortal(\n    this.props.children,\n    domNode,\n  )\n}\n```\n\n使用 `portals` 的典型场景是如果一个父组件有一个 `overflow:hidden` 或者是 `z-index` 的样式，但是你需要子节点在视觉上 `break out` （打破）这个父容器，比如 对话框，选项卡或者提示工具等\n\n下面代码实现一个 `model` 组件\n\n## App.jsx\n\n```jsx\nimport React, { Component } from \'react\'\n\nimport Modal from \'./Modal\'\n\nclass App extends Component {\n  state = { show: false }\n\n  showModal = () => {\n    this.setState({ show: !this.state.show })\n  }\n\n  closeModal = () => {\n    this.setState({ show: false })\n  }\n\n  render() {\n    return (\n      <div className=\"App\">\n        <button onClick={() => this.setState({ show: true })}>open Modal</button>\n\n        <Modal show={this.state.show} onClose={this.closeModal}>\n          This message is from Modal\n        </Modal>\n      </div>\n    )\n  }\n}\n\nexport default App\n```\n\n## Modal.jsx\n\n```jsx\nimport React, { Component } from \'react\'\nimport ReactDOM from \'react-dom\'\n\nconst backdropStyle = {\n  position: \'fixed\',\n  top: 0,\n  bottom: 0,\n  left: 0,\n  right: 0,\n  backgroundColor: \'rgba(0, 0, 0, 0.3)\',\n  padding: 50\n}\n\nconst modalStyle = {\n  backgroundColor: \'#fff\',\n  borderRadius: 5,\n  border: \'1px solid #eee\',\n  maxWidth: 500,\n  minHeight: 300,\n  maring: \'0 auto\',\n  padding: 30,\n  position: \'relative\'\n}\n\nconst footerStyle = {\n  position: \'absolute\',\n  bottom: 20\n}\n\n// 在此前，页面需要创建一个 dom 元素 其中 id 为modal-root\nconst modalRoot = document.getElementById(\'modal-root\')\n\nclass Modal extends Component {\n  constructor(props) {\n    super(props)\n    this.el = document.createElement(\'div\')\n  }\n\n  onKeyUp = e => {\n    // 鼠标信息 http://keycode.info/\n    // 按下 esc\n    if (e.which === 27 && this.props.show) {\n      this.props.onClose()\n    }\n  }\n\n  componentDidMount() {\n    document.addEventListener(\'keyup\', this.onKeyUp)\n    modalRoot.appendChild(this.el)\n  }\n\n  componentWillUnmount() {\n    document.removeEventListener(\'keyup\', this.onKeyUp)\n    modalRoot.removeChild(this.el)\n  }\n\n  render() {\n    if (!this.props.show) return null\n\n    const modalUI = (\n      <div style={backdropStyle}>\n        <div style={modalStyle}>\n          {this.props.children}\n\n          <div style={footerStyle}>\n            <button onClick={this.props.onClose}>Close</button>\n          </div>\n        </div>\n      </div>\n    )\n    // createPortal 挂载到 this.el 的元素中\n    return ReactDOM.createPortal(modalUI, this.el)\n  }\n}\n\nexport default Modal\n```\n', '2019-02-11 12:28:50', '2019-02-23 04:25:35');
INSERT INTO `article` VALUES (13, 'react - PropTypes', '```js\nimport React, { Component } from \'react\'\nimport PropTypes from \'prop-types\'\n\nclass MyComponent extends Component {\n  // static propTypes = {} 第二种写法\n  // static defaultProps = {}\n}\n\n// default props\nMyComponent.defaultProps = {\n  name: \'Stranger\'\n}\n\nMyComponent.PropTypes = {\n  // 声明的prop可以是一个特殊的JS基础变量，默认情况下，下面都是可选的\n  optionalArray: PropTypes.array,\n  optionalBool: PropTypes.bool,\n  optionalFunc: PropTypes.func,\n  optionalNumber: PropTypes.number,\n  optionalObject: PropTypes.object,\n  optionalString: PropTypes.string,\n  optionalSymbol: PropTypes.symbol,\n\n  // 下面示例能够渲染任何元素: numbers, strings, elements ，array, fragment\n  optionalNode: PropTypes.node,\n\n  // 需要是 React 元素\n  optionalElement: PropTypes.element,\n\n  // 可以声明 prop 是某个类的示例\n  optionalMessage: PropTypes.instanceOf(Message),\n\n  // 可以声明 prop 在某个 enum 中的一个\n  optionalEnum: PropTypes.oneOf([\'News\', \'Photos\']),\n\n  // 用来验证prop对象中的每一个属性\n  optionalUnion: PropTypes.oneOfType([\n    PropTypes.string,\n    PropTypes.number,\n    PropTypes.instanceOf(Message)\n  ]),\n\n  // 验证 prop 数组的每个子元素的类型\n  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),\n\n  // 检查 prop 对象的属性的类型\n  optionalObjectOf: PropTypes.objectOf(PropTypes.number),\n\n  // 用来检查 prop 对象的每个属性的类型\n  optionalObjectWithShape: PropTypes.shape({\n    color: PropTypes.string,\n    fontSize: PropTypes.number\n  }),\n\n  // 检查 prop 是必须存在的（required）\n  requiredFunc: PropTypes.func.isRequired,\n\n  // 用来检查任意的数值都必须存在\n  requiredAny: PropTypes.any.isRequired,\n\n  // 你可以通过自定义验证器的方法来进行验证。\n  // 自定义验证器应当返回一个抛出错误的Error对象。\n  // 不要使用`console.warn`或者throw抛出错误，因为无法再 oneOfType 中使用\n  customProp: function(props, propName, componentName) {\n    if (!/matchme/.test(props[propName])) {\n      return new Error(\n        \'Invalid prop `\' + propName + \'` supplied to\' +\n        \' `\' + componentName + \'`. Validation failed.\'\n      );\n    }\n  },\n\n  // 你也可以为\'arrayOf\'和\'objectOf\'提供自定义验证器\n  // 如果验证失败，应该返回一个Error对象\n  // 数组或者对象的每一个key都会被调用这个验证器。\n  // 此验证器的前面两个参数是数组或者是对象本身以及当前遍历的index(如数组下标或对象属性key)\n  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {\n    if (!/matchme/.test(propValue[key])) {\n      return new Error(\n        \'Invalid prop `\' + propFullName + \'` supplied to\' +\n        \' `\' + componentName + \'`. Validation failed.\'\n      );\n    }\n  })\n\n}\n```\n', '2019-02-11 12:29:15', '2019-02-23 04:25:20');
INSERT INTO `article` VALUES (14, 'react - PureComponent 和 memo', '## setState 数据无改变， 组件会重新 render\n\n```jsx\nimport React, { Component } from \'react\'\n\nclass MyComponent extends Component {\n  render() {\n    console.log(\'render\')\n    return null\n  }\n}\n\nclass App extends Component {\n  state = { num: 1 }\n\n  handleClick = () => {\n    this.setState({ num: 1 }) // setState 但是不改变 num\n  }\n\n  render() {\n    return (\n      <div>\n        <MyComponent num={this.state.num} />\n        <button onClick={this.handleClick}>click</button>\n      </div>\n    )\n  }\n}\nexport default App\n```\n\n点击按钮，`setState` 后 `num` 并未发生改变， 但是组件 `MyComponent` 仍然会重新渲染，这就会导致一部分性能的消耗。\n\n我们可以使用 `shouldComponentUpdate(nextProps, nextState)` 来决定组件的渲染与否，也可以使用 react 提供的两个 API\n\n<!--more-->\n\n## React.PureComponent\n\n`pure` 是纯的意思， `PureComponent` 也就是纯组件, 只要把继承类从 `Component` 换成 `PureComponent` 即可，可以减少不必要的 `render` 操作的次数，从而提高性能。\n\n`PureComponent` 主要作用于类组件，而 `memo` 主要作用于函数组件。\n\n> `React.PureComponent` 使用 `prop` 和 `state` 的浅比较来决定是否 `render` 组件。（我们就不需要在 `shouldComponentUpdate` 中写一大段代码了！）\n\n使用方法极其简单（以上面的代码为例）：\n\n```jsx\nimport React, { Component, PureComponent } from \'react\'\n\nclass MyComponent extends PureComponent {\n  render() {\n    console.log(\'render\')\n    return null\n  }\n}\n\n// ... App\n```\n\n### 注意\n\n在不可变数据类型（数组、对象等等）`PureComponent` 是不生效的！因为它的引用地址并未发生改变。做一个 demo:\n\n```jsx\nimport React, { Component, PureComponent } from \'react\'\n\nclass MyComponent extends PureComponent {\n  render() {\n    console.log(\'render\')\n    return this.props.nums\n  }\n}\n\nclass App extends Component {\n  state = { nums: [1, 2, 3] }\n\n  handleClick = () => {\n    const { nums } = this.state\n    nums.pop()\n    this.setState({ nums })\n  }\n\n  render() {\n    return (\n      <div>\n        <MyComponent nums={this.state.nums} />\n        <button onClick={this.handleClick}>click</button>\n      </div>\n    )\n  }\n}\nexport default App\n```\n\n这里无论如何点击按钮，`MyComponent` 也不会重新渲染。具体比较过程是这样的：\n\n```jsx\nclass MyComponent extends Component {\n  shouldComponentUpdate(nextProps, nextState) {\n    // nums 引用是一样的，所以 this.props.nums 等于 nextProps.nums，结果永远返回 false，组件不会重新渲染！\n    return this.props.nums !== nextProps.nums\n  }\n\n  render() {\n    console.log(\'render\')\n    return this.props.nums\n  }\n}\n```\n\n## React.memo\n\n`React.memo` 是一个高阶组件。它与 `React.PureComponent` 类似，但是对于函数组件而不是类。\n\n```jsx\nimport React, { Component, memo } from \'react\'\n\nconst MyComponent = memo(props => {\n  console.log(\'redner\')\n  return null\n})\n\n// ... App\n```\n\n如果你的函数组件在给定相同的道具的情况下呈现相同的结果，则可以 `React.memo` 通过记忆结果将其包装在一些调用中以提高性能。这意味着 `React` 将跳过渲染组件，并重用最后渲染的结果。\n\n默认情况下，它只会浅显比较 `props` 对象中的复杂对象。如果要控制比较，还可以提供自定义比较功能作为第二个参数。\n\n```jsx\nfunction MyComponent(props) {\n  /* render using props */\n}\nfunction areEqual(prevProps, nextProps) {\n  /*\n  return true if passing nextProps to render would return\n  the same result as passing prevProps to render,\n  otherwise return false\n  */\n}\nexport default React.memo(MyComponent, areEqual)\n```\n\n此方法仅作为性能优化存在。不要依赖它来“防止”渲染，因为这可能导致错误。\n', '2019-02-11 12:29:33', '2019-02-23 04:25:00');
INSERT INTO `article` VALUES (15, 'react-redux', '## rudux\n\n`redux` 运行流程图：\n![](https://user-gold-cdn.xitu.io/2018/9/12/165c9daf60abdbd6?w=638&h=479&f=jpeg&s=21322)\n\n简单概述：**click** -> **store.dispatch(action)** -> **reduer** -> **newState** -> **viewUpdate**\n\n**react-readux** 中 通过 **connect** 链接组件和 **redux** , **this.props.dispatch()** 调用\n\n后面将会讲到...\n\n`redux` 依赖包也是十分的简洁\n![](https://user-gold-cdn.xitu.io/2018/9/14/165d8c900fb1fcd4?w=280&h=295&f=png&s=14746)\n先来个`demo`\n\n<!--more-->\n\n```js\nconst redux = require(\'redux\')\nconst createStore = redux.createStore\n\nconst types = {\n  UPDATE_NAME: \'UPDATE_NAME\'\n}\n\nconst defaultStore = {\n  user: \'tom\'\n}\n\n/**\n * reducer 纯函数 接收一个state,返回一个新的state\n * @param {Object} state\n * @param {Object} action [type] 必选参数\n * @return newState\n * */\nfunction getUser(state = defaultStore, action) {\n  const { type, payload } = action\n  let res = Object.assign({}, defaultStore)\n  switch (type) {\n    case types.UPDATE_NAME:\n      res.user = payload.name\n      break\n    default:\n      return res\n  }\n  return res\n}\n\nconst store = createStore(getUser)\n\n/**\n * listener\n * */\nstore.subscribe(() => {\n  console.log(store.getState())\n})\n\n/**\n * dispatch(action) action\n * */\nstore.dispatch({\n  type: types.UPDATE_NAME,\n  payload: {\n    name: \'大帅哥\'\n  }\n})\n//@log { name: \'大帅哥\' }\n```\n\n1. 用户发出 `action` 【`store.dispatch(action)`】\n2. `Store` 自动调用 `Reducer` , 返回新的 `state` 【`let nextState = getUser(previousState, action)`】\n3. `State` 一旦有变化，`Store` 就会调用监听函数 【`store.subscribe(listener)`】\n\n运行过程如下：\n![](https://user-gold-cdn.xitu.io/2018/9/15/165d8e7f3f6d9205?w=635&h=931&f=png&s=95651)\n\n### store\n\n`Store` 就是保存数据的地方，你可以把它看成一个容器。整个应用只能有一个 `Store`\n常用方法：\n\n- store.dispatch() ：分发 action 较为常用\n- store.subscribe() : state 发生变化后立即执行\n- store.getState() : 获取 store 中存着的 state\n\n### createStore\n\n[createStore](https://github.com/reduxjs/redux/blob/master/src/createStore.js) 如其名，创建 `store` 下面是该方法的部分源码：\n\n```js\n/**\n * @param {Function} reducer 函数\n * @param {any} [preloadedState] The initial state\n * @param {Function} [enhancer] The store enhancer\n * @returns {Store}\n * */\nexport default function createStore(reducer, preloadedState, enhancer) {\n  if (typeof preloadedState === \'function\' && typeof enhancer === \'undefined\') {\n    enhancer = preloadedState\n    preloadedState = undefined\n  }\n\n  if (typeof enhancer !== \'undefined\') {\n    if (typeof enhancer !== \'function\') {\n      throw new Error(\'Expected the enhancer to be a function.\')\n    }\n\n    return enhancer(createStore)(reducer, preloadedState)\n  }\n  // ...\n  return {\n    dispatch, // 分发 action\n    subscribe, // 监听器\n    getState, // 获取 store 的 state 值\n    replaceReducer,\n    [$$observable]: observable // 供Redux内部使用\n  }\n}\n```\n\n- `preloadedState`: 初始化的`initialState`，第二个参数不是`Object`,而是`Function`，`createStore`会认为你忽略了`preloadedState`而传入了一个`enhancer`\n- `createStore`会返回`enhancer(createStore)(reducer, preloadedState)`的调用结果，这是常见高阶函数的调用方式。在这个调用中`enhancer`接受`createStore`作为参数，对`createStore`的能力进行增强，并返回增强后的`createStore`\n\n### dispatch(action)\n\n`diapatch` 是 store 对象的方法，主要用来分发 `action` ,\n\n> redux 规定 action 一定要包含一个 type 属性，且 type 属性也要唯一\n\ndispatch 是 store 非常核心的一个方法，也是我们在应用中最常使用的方法，下面是 dispatch 的源码 ：\n\n```js\nfunction dispatch(action) {\n  if (!isPlainObject(action)) {\n    // 校验了action是否为一个原生js对象\n    throw new Error(\'Actions must be plain objects. \' + \'Use custom middleware for async actions.\')\n  }\n\n  if (typeof action.type === \'undefined\') {\n    // action对象是否包含了必要的type字段\n    throw new Error(\'Actions may not have an undefined \"type\" property. \' + \'Have you misspelled a constant?\')\n  }\n\n  if (isDispatching) {\n    // 判断当前是否处于某个action分发过程中, 主要是为了避免在reducer中分发action\n    throw new Error(\'Reducers may not dispatch actions.\')\n  }\n\n  try {\n    isDispatching = true\n    currentState = currentReducer(currentState, action)\n  } finally {\n    isDispatching = false\n  }\n\n  const listeners = (currentListeners = nextListeners)\n  for (let i = 0; i < listeners.length; i++) {\n    const listener = listeners[i]\n    listener()\n  }\n  // 在一系列检查完毕后，若均没有问题，将当前的状态和action传给当前reducer，用于生成新的state\n  return action\n}\n```\n\n### reducer && store.replaceReducer\n\nRedux 中负责响应 action 并修改数据的角色就是`reducer`，`reducer`的本质实际上是一个函数\nreplaceReducer:\n\n```js\n/**\n * @desc 替换当前的reducer的函数\n * @param {Function}\n * @return {void}\n */\nfunction replaceReducer(nextReducer) {\n  if (typeof nextReducer !== \'function\') {\n    throw new Error(\'Expected the nextReducer to be a function.\')\n  }\n\n  currentReducer = nextReducer\n  dispatch({ type: ActionTypes.REPLACE })\n}\n```\n\nreplaceReducer 使用场景：\n\n- 当你的程序要进行代码分割的时候\n- 当你要动态的加载不同的 reducer 的时候\n- 当你要实现一个实时 reloading 机制的时候\n\n### 中间件 middleware\n\n以上介绍了 redux 的实现流的过程，应用场景无非于\n\nbutton -- click --> `disptch` -- action --> `reducer` -- newState --> `view`\n\n但是这种实现方式是基于同步的方式的，日常开发中当然少不了 http 这些异步请求，这种情况下必须等到服务器数据返回后才重新渲染 view, 显然某些时候回阻塞页面的展示。\n\n举例来说，要添加日志功能，把 `Action` 和 `State` 打印出来，可以对 store.dispatch 进行如下改造。\n\n```js\nlet next = store.dispatch\nstore.dispatch = function dispatchAndLog(action) {\n  console.log(\'dispatching\', action)\n  next(action)\n  console.log(\'next state\', store.getState())\n}\n```\n\n上面代码中，对 store.dispatch 进行了重定义，在发送 Action 前后添加了打印功能。这就是中间件的雏形。\n\n中间件就是一个函数，对 store.dispatch 方法进行了改造，在发出 Action 和执行 Reducer 这两步之间，添加了其他功能。\n\n### applyMiddleware\n\nRedux 提供了`applyMiddleware`来装载`middleware`：\n它是 Redux 的原生方法，**作用是将所有中间件组成一个数组，依次执行。**下面是它的源码。\n\n```js\n/**\n * @param {...Function} middlewares\n * returns {Function} A store enhancer applying the middleware\n */\nexport default function applyMiddleware(...middlewares) {\n  return createStore => (...args) => {\n    const store = createStore(...args)\n    let dispatch = () => {\n      throw new Error(\n        `Dispatching while constructing your middleware is not allowed. ` +\n          `Other middleware would not be applied to this dispatch.`\n      )\n    }\n\n    const middlewareAPI = {\n      getState: store.getState,\n      dispatch: (...args) => dispatch(...args)\n    }\n    const chain = middlewares.map(middleware => middleware(middlewareAPI))\n    dispatch = compose(...chain)(store.dispatch)\n\n    return {\n      ...store,\n      dispatch\n    }\n  }\n}\n```\n\n所有中间件被放进了一个数组 chain，然后嵌套执行，最后执行 store.dispatch。可以看到，中间件内部（middlewareAPI）可以拿到`getState`和`dispatch`这两个方法\n\n`compose` 实际上是函数式编程中的组合，接收多个函数体并且将其组合成一个新的函数，例如`compose` 后 [fn1, fn2...] 依次从右到左嵌套执行函数 而`compose`用于`applyMiddleware` 也是为了组合中间件\n**dispatch = compose(...chain)(store.dispatch)**\n==>\n**dispatch=fn1(fn2(fn3(store.dispatch)))**\n\n```js\n/**\n * @param {...Function} funcs The functions to compose.\n * @returns {Function} A function obtained by composing the argument functions\n */\nexport default function compose(...funcs) {\n  if (funcs.length === 0) {\n    return arg => arg\n  }\n\n  if (funcs.length === 1) {\n    return funcs[0]\n  }\n\n  return funcs.reduce((a, b) => (...args) => a(b(...args)))\n}\n```\n\n### redux-thunk\n\n上面的中间件的介绍可以知道\nredux 通过 `applyMiddleware` 来装载中间件，通过 compose 方法可以组合函数\n\n异步的问题可以通过 `redux-thunk` 解决，用法也不难 react 组件中使用相关如下：\n\n```js\n// 配置 redux 加上这个...\nimport { createStore, applyMiddleware, compose } from \'redux\'\nimport thunk from \'redux-thunk\'\n// ...\nconst store = createStore(getUser, compose(applyMiddleware(thunk)))\n\n// react 中使用\nimport { connect } from \'react-redux\'\n\nhandleClick = () => {\n  this.props.dispatch(dispatch => {\n    return axios.get(\'https://randomuser.me/api/\').then(res => {\n      dispatch({\n        type: types.CHANGE_ARRAY,\n        payload: {\n          name: res.data.results[0].name.title\n        }\n      })\n    })\n  })\n}\n\nconst mapStateToProps = (state, props) => {\n  return {\n    name: state.demo.name\n  }\n}\n\nexport default connect(mapStateToProps)(Demo)\n```\n\n> 处理异步的还有很多插件 如 redux-soga 等，楼主并未实践过，所以不做延伸...\n\n## react-redux\n\n下面是在 react 中使用的代码的雏形：\n\n```jsx\nimport { createStore } from \'redux\'\n\nlet defaultState = {\n  count: 1\n}\n\n/**\n * Reducer\n * */\nfunction demoReducer(state = defaultState, action = {}) {\n  const { type, payload } = action\n  const res = Object.assign({}, state)\n  if (type === \'changeCount\') {\n    res.count = payload.count\n  }\n  return res\n}\n\n/**\n * @Store 存数据的地方，你可以把它看成一个容器。整个应用只能有一个 Store。\n * combineReducers({ ...reducers }) 可以组合多个reducer\n * */\nconst store = createStore(\n  demoReducer,\n  window.devToolsExtension && window.devToolsExtension() // 配置redux 开发工具\n)\n\n// ... 根元素下配置下 Provider\nimport { Provider } from \'react-redux\'\n\nReactDOM.render(\n  <Provider store={store}>\n    <App />\n  </Provider>,\n  document.getElementById(\'root\')\n)\n\n// 组件中使用\nimport { connect } from \'react-redux\'\n\n//use\nthis.dispatch({\n  type: \'changeCount\',\n  payload: {\n    count: 22\n  }\n})\n\nconst mapStateToProps = (state, props) => {\n  return {\n    name: state.demo.name\n  }\n}\n\nexport default connect(mapStateToProps)(Demo)\n```\n\n### mapStateToProps\n\n- 用于建立组件跟 store 的 state 的映射关系作为一个函数，它可以传入两个参数，结果一定要返回一个 object\n- 传入`mapStateToProps`之后，会订阅 store 的状态改变，在每次 store 的 state 发生变化的时候，都会被调用\n- 如果写了第二个参数 props，那么当 props 发生变化的时候，mapStateToProps 也会被调用\n\n### mapDispatchToProps\n\n- `mapDispatchToProps`用于建立组件跟 store.dispatch 的映射关系\n- 可以是一个 object，也可以传入函数\n- 如果`mapDispatchToProps`是一个函数，它可以传入 dispatch,props,定义 UI 组件如何发出 action，实际上就是要调用 dispatch 这个方法\n\n```js\nimport { connect } from \'react-redux\'\nimport { bindActionCreators } from \'redux\'\n\n// 页面中使用...\nthis.props.changeName()\n\nconst mapDispatchToProps = ({ changeName } = (dispatch, props) => {\n  return bindActionCreators(\n    {\n      changeName: function() {\n        return {\n          type: types.UPDATE_NAME,\n          payload: {\n            name: \'大大大\'\n          }\n        }\n      }\n    },\n    dispatch\n  )\n})\n\nexport default connect(mapDispatchToProps)(App)\n```\n\n## 模块化配置\n\n下面的配置仅供参考。实现的功能：\n\n- 整合 `action`、`types`、`reducer` 到一个文件\n- 根据开发/生成环境配置不同的 `redux` 中间件(开发环境配置 `dev-tools` )\n- 支持装饰器模式\n- `redux` 热加载配置（这里面顺便将 `react` 热加载配置也加上了）\n\n注意：项目基于 `create-react-app` `eject` 后的配置改造实现的。下面用了别名 @ ，需要改下 `webpack` 的配置，如果你配置不成功。详情可以看我的 `github` 上面有源码. [链接入口](https://github.com/gershonv/react-demo)\n\n### 安装\n\n```\nnpm install redux react-redux redux-thunk --save\nnpm install redux-devtools-extension react-hot-loader -D\nnpm install @babel/plugin-proposal-decorators -D\n```\n\n相关文件夹如图：\n![](https://user-gold-cdn.xitu.io/2018/12/11/1679c389904fbb55?w=368&h=300&f=png&s=16726)\n\n#### models/demo.js\n\ndemo 模块。\n\n```js\n// types\nconst ADD_COUNT = \'ADD_COUNT\'\n\n// actions\nexport const addCount = () => {\n  return { type: ADD_COUNT }\n}\n\n// state\nconst defaultState = {\n  count: 11\n}\n\n// reducer\nexport const demoReducer = (state = defaultState, action) => {\n  switch (action.type) {\n    case ADD_COUNT:\n      return { ...state, count: ++state.count }\n    default:\n      return state\n  }\n}\n\nexport default demoReducer\n```\n\n#### models/index.js\n\n模块的导出口。\n\n```js\nimport { combineReducers } from \'redux\'\n\nimport demo from \'./demo\'\n\nexport default combineReducers({\n  demo\n})\n```\n\n#### redux/index.js\n\n`redux` 仓库的总出口\n\n```js\nimport thunk from \'redux-thunk\'\nimport { compose, createStore, applyMiddleware } from \'redux\'\nimport { composeWithDevTools } from \'redux-devtools-extension\'\n\nimport rootReducer from \'./models\'\n\nlet storeEnhancers\nif (process.env.NODE_ENV === \'production\') {\n  storeEnhancers = compose(thunk)\n} else {\n  storeEnhancers = compose(composeWithDevTools(applyMiddleware(thunk)))\n}\n\nconst configureStore = (initialState = {}) => {\n  const store = createStore(rootReducer, initialState, storeEnhancers)\n\n  if (module.hot && process.env.NODE_ENV !== \'production\') {\n    // Enable Webpack hot module replacement for reducers\n    module.hot.accept(\'./models\', () => {\n      console.log(\'replacing reducer...\')\n      const nextRootReducer = require(\'./models\').default\n      store.replaceReducer(nextRootReducer)\n    })\n  }\n\n  return store\n}\n\nexport default configureStore()\n```\n\n#### src/index.js\n\nreact 项目的入口配置。\n\n```js\nimport React from \'react\'\nimport ReactDOM from \'react-dom\'\nimport { AppContainer } from \'react-hot-loader\'\nimport App from \'./App\'\nimport { Provider } from \'react-redux\'\nimport store from \'@/redux\'\n\nconst render = Component => {\n  ReactDOM.render(\n    <AppContainer>\n      <Provider store={store}>\n        <Component />\n      </Provider>\n    </AppContainer>,\n    document.getElementById(\'root\')\n  )\n}\n\nrender(App)\n\nif (module.hot) {\n  module.hot.accept(\'./App\', () => {\n    render(App)\n  })\n}\n```\n\n#### App.jsx\n\n```js\nimport React, { Component, Fragment } from \'react\'\nimport { connect } from \'react-redux\'\nimport { addCount } from \'@/redux/models/demo\'\nimport { Button } from \'antd\'\n\nconst mapStateToProps = state => ({\n  count: state.demo.count\n})\n\n@connect(\n  mapStateToProps,\n  { addCount }\n)\nclass ReduxTest extends Component {\n  render() {\n    return (\n      <Fragment>\n        {this.props.count}\n        <Button type=\"primary\" onClick={this.props.addCount}>\n          Click\n        </Button>\n        <hr />\n      </Fragment>\n    )\n  }\n}\n\nexport default ReduxTest\n```\n\n#### .babelrc\n\n配置 babel 装饰器模式\n\n```js\n{\n  \"presets\": [\"react-app\"],\n  \"plugins\": [\n    [\"@babel/plugin-proposal-decorators\", { \"legacy\": true }]\n  ]\n}\n```\n\nvscode 装饰器模式如果有报警的话，可以根目录下新建 `jsconfig.json`\n\n```js\n{\n  \"compilerOptions\": {\n    \"experimentalDecorators\": true,\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"@/*\": [\n        \"src/*\"\n      ]\n    },\n    \"jsx\": \"react\"\n  },\n  \"exclude\": [\n    \"node_modules\",\n    \"build\",\n    \"config\",\n    \"scripts\"\n  ]\n}\n```\n\n## 参考\n\n- [阮一峰 redux 入门教程](http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html)\n- 配置文件可以看我的 github : [react-demo](https://github.com/gershonv/react-demo)', '2019-02-11 12:31:15', '2019-02-11 12:31:15');
INSERT INTO `article` VALUES (16, 'react - Ref', '在 `react` 典型的数据流中，`props` 传递是父子组件交互的唯一方式；要修改子组件，你需要使用新的 props 重新渲染它。\n但是，某些情况下你需要在典型数据流外强制修改子组件。某些情况下（例如和第三方的 dom 库整合，或者某个 dom 元素 focus 等）为了修改子组件我们可能需要另一种方式，这就是 `ref` 方式。\n\n<!-- more -->\n\n## ref 简介\n\n`React` 提供的这个 `ref` 属性，表示为对组件真正实例的引用，其实就是 `ReactDOM.render()`返回的组件实例；需要区分一下，`ReactDOM.render()`渲染组件时返回的是组件实例；而渲染 `dom` 元素时，返回是具体的 `dom` 节点。\n\n> 那么我们可以知道 `ref` 挂载到普通 `dom` 节点时代表这个 `dom` 元素，相当于 `document.querySelect()`, 而挂载到组件时返回的是这个组件的实例（我们可以直接访问组件的 `state` 和方法）\n\n```js\nclass Demo extends Component {\n  state = { name: \'demo\' }\n  render() {\n    return null\n  }\n}\n\nclass App extends Component {\n  componentDidMount() {\n    this.input.focus() // 控制 input 元素的聚焦\n    console.log(this.demo.state) // { name: \'demo\' }\n  }\n\n  render() {\n    return (\n      <div>\n        <input type=\"text\" ref={el => (this.input = el)} />\n        <Demo ref={el => (this.demo = el)} />\n      </div>\n    )\n  }\n}\n```\n\n## 通过回调方式设置 ref（推荐）\n\nref 属性可以设置为一个回调函数，这也是官方强烈推荐的用法；这个函数执行的时机为：\n\n- 组件被挂载后，回调函数被立即执行，回调函数的参数为该组件的具体实例。\n- 组件被卸载或者原有的 `ref` 属性本身发生变化时，回调也会被立即执行，此时回调函数参数为 `null`，以确保内存泄露。\n\n```js\nclass Demo extends Component {\n  render() {\n    return <span>demo</span>\n  }\n}\n\nclass App extends Component {\n  state = { visible: true }\n\n  toggleVisible = () => {\n    this.setState({ visible: !this.state.visible })\n  }\n\n  refCb = instance => {\n    console.log(instance)\n  }\n\n  render() {\n    return (\n      <div>\n        <button onClick={this.toggleVisible}>toggle</button>\n        {this.state.visible && <Demo ref={this.refCb} />}\n      </div>\n    )\n  }\n}\n// demo mounted => run refCb 返回 Demo 组件实例对象\n// demo destory => run refCb 返回 null\n```\n\n## 其他方式设置 ref\n\n### createRef\n\n```js\nclass App extends Component {\n  constructor(props) {\n    super(props)\n    this.inputRef = React.createRef()\n  }\n\n  componentDidMount() {\n    this.inputRef.current.focus()\n  }\n\n  render() {\n    return <input type=\"text\" ref={this.inputRef} />\n  }\n}\n```\n\n### ref=\'xxx\'（不推荐）\n\n```jsx\n<input ref=\"inputRef\" />\n\n// this.refs.inputRef 访问这个 dom 元素\n```\n\n## 在函数组件中使用 ref\n\n函数组件，即无状态组件`stateless component`在创建时是不会被实例化的。\n无状态组件内部其实是可以使用 ref 功能的，虽然不能通过 this.refs 访问到，但是可以通过将 ref 内容保存到无状态组件内部的一个本地变量中获取到。\n\n```jsx\nimport React from \'react\'\n\nconst App = () => {\n  let inputRef\n  function handleClick() {\n    inputRef.focus()\n  }\n  return (\n    <div>\n      <button onClick={handleClick}>focus</button>\n      <input type=\"text\" ref={el => (inputRef = el)} />\n    </div>\n  )\n}\n\nexport default App\n```\n\n## React.forwardRef\n\n> `Ref forwarding` 是一种自动将 `ref` 通过组件传递给其子节点的技术。\n\nRef 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref。也就是说我们不想控制子组件，我们想具体控制到子组件某个组件的实例。\n\n```js\nconst FancyButton = React.forwardRef((props, ref) => (\n  <button ref={ref} className=\"FancyButton\">\n    {props.children}\n  </button>\n))\n\nclass App extends Component {\n  componentDidMount() {\n    console.log(this.fButton) //  <button className=\"FancyButton\">click</button>\n  }\n\n  render() {\n    return <FancyButton ref={node => (this.fButton = node)}>click</FancyButton>\n  }\n}\n```\n\n举例个应用场景，我们自封装一个 `Input` 组件，那我们的 `ref` 是要具体控制到原生的 `input`。以下是具体得代码：\n\n```js\nclass Input extends Component {\n  render() {\n    const { forwardedRef, ...rest } = this.props\n    return <input {...rest} ref={forwardedRef} type=\"text\" />\n  }\n}\n\nconst MyInput = React.forwardRef((props, ref) => <Input {...props} forwardedRef={ref} />)\n\nclass App extends Component {\n  componentDidMount() {\n    this.input.focus()\n  }\n\n  render() {\n    return <MyInput defaultValue=\"guodada\" ref={el => (this.input = el)} />\n  }\n}\n```\n\n## ref 在 HOC 中存在的问题\n\n`react` 的 HOC 是高阶组件，简单理解就是包装了一个低阶的组件，最后返回一个高阶的组件；高阶组件其实是在低阶组件基础上做了一些事情。\n\n既然 `HOC` 会基于低阶组件生成一个新的高阶组件，若用 `ref` 就不能访问到我们真正需要的低阶组件实例，我们访问到的其实是高阶组件实例。所以:\n\n```js\nconst Hoc = WrappedComponent =>\n  class extends Component {\n    render() {\n      return <WrappedComponent {...this.props} />\n    }\n  }\n\nclass Demo extends Component {\n  state = { name: \'guodada\' }\n  render() {\n    return null\n  }\n}\n\nconst Test = Hoc(Demo)\n\nclass App extends Component {\n  componentDidMount() {\n    console.log(this.demoRef.state) // null, this.demoRef 不是 Demo 组件的实例\n  }\n\n  render() {\n    return <Test ref={node => (this.demoRef = node)} />\n  }\n}\n```\n\n## 总结\n\n`ref` 提供了一种对于 `react` 标准的数据流不太适用的情况下组件间交互的方式，例如管理 dom 元素 focus、text selection 以及与第三方的 dom 库整合等等。 但是在大多数情况下应该使用 react 响应数据流那种方式，不要过度使用 ref。\n\n另外，在使用 ref 时，不用担心会导致内存泄露的问题，react 会自动帮你管理好，在组件卸载时 ref 值也会被销毁。\n\n最后补充一点：\n\n> 不要在组件的 `render` 方法中访问 `ref` 引用，`render` 方法只是返回一个虚拟 dom，这时组件不一定挂载到 `dom` 中或者 `render` 返回的虚拟 dom 不一定会更新到 dom 中。\n\n参考 [React 之 ref 详细用法](https://segmentfault.com/a/1190000008665915)\n', '2019-02-11 12:31:48', '2019-02-11 12:31:48');
INSERT INTO `article` VALUES (17, 'react-router - [译] quickstart', '## Quick start - 快速开始\n\n```jsx\nimport React from \'react\'\nimport { BrowserRouter as Router, Route, Link } from \'react-router-dom\'\n\nconst Index = () => <h2>Home</h2>\nconst About = () => <h2>About</h2>\nconst Users = () => <h2>Users</h2>\n\nconst AppRouter = () => (\n  <Router>\n    <div>\n      <nav>\n        <ul>\n          <Link to=\"/\">Home</Link>\n          <Link to=\"/about/\">About</Link>\n          <Link to=\"/users/\">Users</Link>\n        </ul>\n      </nav>\n\n      <Route path=\"/\" exact component={Index} />\n      <Route path=\"/about/\" component={About} />\n      <Route path=\"/users/\" component={Users} />\n    </div>\n  </Router>\n)\n\nexport default AppRouter\n```\n<!--more-->\n\n## Nested Routing -嵌套路由\n\n```jsx\nimport React from \'react\'\nimport { BrowserRouter as Router, Route, Link } from \'react-router-dom\'\n\nconst App = () => (\n  <Router>\n    <div>\n      <nav>\n        <Link to=\"/\">Home</Link>\n        <Link to=\"/about/\">About</Link>\n        <Link to=\"/topics\">Topics</Link>\n      </nav>\n\n      <Route exact path=\"/\" component={Home} />\n      <Route path=\"/about\" component={About} />\n      <Route path=\"/topics\" component={Topics} />\n    </div>\n  </Router>\n)\n\nconst Home = () => <h2>Home</h2>\nconst About = () => <h2>About</h2>\n\n/**\n * @param props\n * Route 组件中 component 的 props 中会传递 match 属性\n * @example /topics/components\n *          match `isExact`: true ; `params`: {id: \"components\"} ; `path`: \"/topics/:id\"; `url`: \"/topics/components\"\n */\nconst Topic = ({ match }) => <h3>Requested Param: {match.params.id}</h3>\n\n\nconst Topics = ({ match }) => (\n  <div>\n    <h2>Topics</h2>\n    <Link to={`${match.url}/components`}>Components</Link>\n    <Link to={`${match.url}/props-v-state`}>Props v. State</Link>\n    <Route path={`${match.path}/:id`} component={Topic} />\n    <Route\n      exact\n      path={match.path}\n      render={() => <h3>Please select a topic.</h3>}\n    />\n  </div>\n)\n\nexport default App\n```', '2019-02-11 12:37:48', '2019-02-20 05:44:12');
INSERT INTO `article` VALUES (18, 'react-router - [译] Basic Components', '## Routers\n\n> At the core of every React Router application should be a router component. For web projects, react-router-dom provides <BrowserRouter> and <HashRouter> routers. Both of these will create a specialized history object for you. Generally speaking, you should use a <BrowserRouter> if you have a server that responds to requests and a <HashRouter> if you are using a static file server.\n\n每个 `React Router` 应用程序的核心应该是路由器组件。对于 Web 项目，`react-router-dom` 提供`<BrowserRouter>`和`<HashRouter>`路由器。\n这两个都将为您创建一个专门的历史对象。\n\n**一般来说，如果您有响应请求的服务器，则应使用`<BrowserRouter>`;如果使用静态文件服务器，则应使用`<HashRouter>`。**\n\n<!--more-->\n\n`Router` 组件本身只是一个容器，真正的路由要通过 `Route` 组件定义:\n\n## Route Matching\n\nThere are two route matching components: `<Route>` and `<Switch>.`\n\n> Route matching is done by comparing a <Route>\'s path prop to the current location’s pathname. When a <Route> matches it will render its content and when it does not match, it will render null. A <Route> with no path will always match.\n\n1. 路由匹配是通过将`<Route>`的路径 `prop` 与当前位置的路径名进行比较来完成的。\n2. 当`<Route>`匹配时，它将呈现其内容，当它不匹配时，它将呈现为 `null`。\n3. 没有路径的`<Route>`将始终匹配。\n\n> The <Switch> is not required for grouping <Route>s, but it can be quite useful. A <Switch> will iterate over all of its children <Route> elements and only render the first one that matches the current location. This helps when multiple route’s paths match the same pathname, when animating transitions between routes, and in identifying when no routes match the current location (so that you can render a “404” component).\n\n`<Switch>` 组件用于包裹 `<Route>` 组件, `<Switch>` 将迭代其所有子 `<Route>` 元素，并仅渲染与当前位置匹配的第一个子元素。\n当多个路径的路径匹配相同的路径名，动画路径之间的转换，以及识别何时没有路径与当前位置匹配（这样您可以渲染“404”组件）时，这会有所帮助。\n\n```jsx\nimport React from \'react\'\nimport { BrowserRouter, Route, Switch } from \'react-router-dom\'\n\nconst About = () => <h1>About</h1>\nconst Home = () => <h1>Home</h1>\nconst NoFound = () => <h1>NoFound</h1>\n\n/**\n * @desc Switch 组件包裹，匹配第一个匹配到路由的组件\n * 1. 没有 exact 属性，则永远都能匹配到第一个组件，不会匹配到下面的路由\n * 2. 都匹配不中时，匹配到最后一个组件。可以作为404\n */\nconst App = () => {\n  return (\n    <BrowserRouter>\n      <Switch>\n        <Route exact path=\"/\" component={Home} />\n        <Route path=\"/about\" component={About} />\n        <Route component={NoFound} />\n      </Switch>\n    </BrowserRouter>\n  )\n}\n\nexport default App\n```\n\n## Route Rendering Props\n\n对于给定<Route>的组件呈现方式，您有三个 prop 选项：component，render 和 children。\n\n> component should be used when you have an existing component (either a React.Component or a stateless functional component) that you want to render. render, which takes an inline function, should only be used when you have to pass in-scope variables to the component you want to render. You should not use the component prop with an inline function to pass in-scope variables because you will get undesired component unmounts/remounts.\n\n- `component`: 在你需要呈现现有的组件，应使用该属性。\n- `render`: 采用内联函数，当你需要传递变量给渲染的组件时，才使用。\n\n```jsx\n// 正确的使用方式\nconst name = \'guodada\'\n<Route exact path=\"/\" component={Home} />\n<Route\n  path=\"/about\"\n  render={props => <About {...props} name={name} />}/>\n\n// incorrect\n<Route\n  path=\"/about\"\n  component={props => <About {...props} name={name} />}/>\n```\n\n### children - 使用在嵌套路由中\n\n```jsx\nimport React from \'react\'\nimport { BrowserRouter as Router, Switch, Route } from \'react-router-dom\'\n\nconst Home = () => <h1>Home</h1>\n\nclass App extends React.Component {\n  render() {\n    return (\n      <div>\n        <header>我是 header</header>\n        {this.props.children}\n        <footer>我是 footer</footer>\n      </div>\n    )\n  }\n}\n\nconst Routes = () => (\n  <Router>\n    <Switch>\n      <Route\n        path=\"/\"\n        render={props => (\n          <App {...props}>\n            <Route path=\"/home\" component={Home} />\n          </App>\n        )}\n      />\n    </Switch>\n  </Router>\n)\n\nexport default Routes\n```\n\n上面代码中，用户访问/home 时，会先加载 App 组件，然后在它的内部再加载 Home 组件\n\n上面代码中，App 组件的 this.props.children 属性就是子组件。\n\n子路由也可以不写在 Router 组件里面，单独传入 Router 组件的 routes 属性。\n\n```jsx\nconst routes = (\n  <Route path=\"/\" component={App}>\n    <Route path=\"/home\" component={Home} />\n    <Route path=\"/about\" component={About} />\n  </Route>\n)\n\n<Router routes={routes} history={browserHistory} />\n```\n\n## Navigation - 导航\n\n> React Router provides a <Link> component to create links in your application. Wherever you render a <Link>, an anchor (<a>) will be rendered in your application’s HTML.\n\n`React Router` 提供了一个`<Link>`组件来在您的应用程序中创建链接。无论何处呈现<Link>，锚点（<a>）都将在应用程序的 HTML 中呈现\n\n```jsx\nimport { NavLink } from \'react-router-dom\'\n\n// location = { pathname: \'/react\' }\n<NavLink to=\"/react\" activeClassName=\"hurray\">\n  React\n</NavLink>\n```', '2019-02-11 12:38:13', '2019-02-20 05:43:58');
INSERT INTO `article` VALUES (19, 'react-router - [译] Code Splitting', '\n> One great feature of the web is that we don’t have to make our visitors download the entire app before they can use it. You can think of code splitting as incrementally downloading the app. To accomplish this we’ll use webpack, @babel/plugin-syntax-dynamic-import, and react-loadable.\n\n`Code Spliting` 的一个重要特性是我们不必让访问者在使用它之前下载整个应用程序。您可以将代码拆分视为逐步下载应用程序。为此，我们将使用 webpack，[@babel/plugin-syntax-dynamic-import](https://www.npmjs.com/package/@babel/plugin-syntax-dynamic-import) 和 [react-loadable](https://www.npmjs.com/package/react-loadable)。\n\n<!--more-->\n\n```\nyarn add @babel/plugin-syntax-dynamic-import -D\nyarn add react-loadable\n```\n\n> webpack has built-in support for dynamic imports; however, if you are using Babel (e.g., to compile JSX to JavaScript) then you will need to use the @babel/plugin-syntax-dynamic-import plugin. This is a syntax-only plugin, meaning Babel won’t do any additional transformations. The plugin simply allows Babel to parse dynamic imports so webpack can bundle them as a code split. Your .babelrc should look something like this:\n\nwebpack 内置了对动态导入的支持;但是，如果您使用 Babel（例如，将 JSX 编译为 JavaScript），那么您将需要使用 @babel/plugin-syntax-dynamic-import 插件。这是一个仅限语法的插件，这意味着 Babel 不会进行任何其他转换。该插件只允许 Babel 解析动态导入，因此 webpack 可以将它们捆绑为代码拆分。你的.babelrc 应该是这样的：\n\n```json\n{\n  \"presets\": [\"@babel/react\"],\n  \"plugins\": [\"@babel/plugin-syntax-dynamic-import\"]\n}\n```\n\n> react-loadable is a higher-order component for loading components with dynamic imports. It handles all sorts of edge cases automatically and makes code splitting simple! Here’s an example of how to use react-loadable:\n\nreact-loadable 是一个高阶组件，用于加载具有动态导入的组件。它自动处理各种边缘情况，使代码分割变得简单！以下是如何使用 react-loadable 的示例：\n\n新建 `components/About.jsx` `components/Loading.jsx`:\n\n```jsx\nimport React from \'react\'\nimport Loadable from \'react-loadable\'\nimport Loading from \'./components/Loading\'\n\nconst LoadableComponent = Loadable({\n  loader: () => import(\'./components/About\'),\n  loading: Loading\n})\n\nexport default class App extends React.Component {\n  render() {\n    return <LoadableComponent />\n  }\n}\n```\n\n只需使用 `LoadableComponent`（或任何您命名的组件），当您在应用程序中使用它时，它将自动加载和呈现。`loader` 选项是一个实际加载组件的函数，而 `loading` 是一个占位符组件，用于在实际组件加载时显示。\n\n## Code-splitting + server rendering | 代码分割与服务端渲染\n\n> react-loadable includes a guide for server-side rendering. All you should need to do is include babel-plugin-import-inspector in your .babelrc and server-side rendering should just work™. Here is an example .babelrc file:\n\n`react-loadable` 包括服务器端呈现的指南。您需要做的就是在`.babelrc` 中包含 `babel-plugin-import-inspector`，服务器端渲染应该只是`work™`。这是一个示例`.babelrc` 文件：\n\n```json\n{\n  \"presets\": [\"@babel/react\"],\n  \"plugins\": [\n    \"@babel/plugin-syntax-dynamic-import\",\n    [\n      \"import-inspector\",\n      {\n        \"serverSideRequirePath\": true\n      }\n    ]\n  ]\n}\n```\n\n## 相关介绍\n\n[React Loadable - 以组件为中心的代码分割和懒加载](https://www.jianshu.com/p/697669781276)', '2019-02-11 12:38:40', '2019-02-20 05:43:48');
INSERT INTO `article` VALUES (20, 'react-router - 常用知识', '## 组件-Router\n\n```jsx\n// 用于导航的历史对象\n<Router history={history}></Router>\n\n// 一个使用了 HTML5 history API 的高阶路由组件，保证你的 UI 界面和 URL 保持同步\n<BrowserRouter\n    basename=\"/calendar\" // 添加一个基准URL\n    forceRefresh={false} // 当浏览器不支持 HTML5 的 history API 时强制刷新页面\n    getUserConfirmation={getConfirmation()} // 导航到此页面前执行的函数\n    keyLength={12} // 设置它里面路由的 location.key 的长度。默认是6\n></BrowserRouter>\n\n<HashRouter></HashRouter>\n// Hash history 不支持 location.key 和 location.state。\n// 另外由于该技术只是用来支持旧版浏览器，因此更推荐大家使用 BrowserRouter\n```\n<!--more-->\n\n### forceRefresh: bool\n\n如果为 `true`，路由器将在页面导航时使用完整页面刷新。您可能只想在不支持 `HTML5` 历史记录 `API` 的浏览器中使用它。\n\n```jsx\nconst supportsHistory = \'pushState\' in window.history\n<BrowserRouter forceRefresh={!supportsHistory}/>\n```\n\n### getUserConfirmation: func\n\n用于确认导航的函数，默认使用 `window.confirm`。例如，当从 /a 导航至 /b 时，会使用默认的 `confirm` 函数弹出一个提示，用户点击确定后才进行导航，否则不做任何处理。译注：需要配合 `<Prompt>` 一起使用。\n\n```jsx\n// this is the default behavior\nconst getConfirmation = (message, callback) => {\n  const allowTransition = window.confirm(message)\n  callback(allowTransition)\n}\n\n<BrowserRouter getUserConfirmation={getConfirmation}/>\n```\n\n### keyLength: number\n\nThe length of location.key. Defaults to 6.\n\n## 组件-Switch\n\n如果你访问 `/about`，那么组件 About User Nomatch 都将被渲染出来，因为他们对应的路由与访问的地址 `/about` 匹配\n\n```jsx\n// render all\n<Route path=\"/about\" component={About}/>\n<Route path=\"/:user\" component={User}/>\n<Route component={NoMatch}/>\n```\n\n**`<Switch>`只渲染出第一个与当前访问地址匹配的 `<Route>` 或 `<Redirect>`**\n\n```jsx\n// Renders the first child <Route> or <Redirect> that matches the location.\n<Switch>\n  <Route exact path=\"/\" component={Home} />\n  <Route path=\"/about\" component={About} />\n  <Route path=\"/:user\" component={User} />\n  <Route component={NoMatch} />\n</Switch>\n```\n\n## 组件-Route\n\n```jsx\n<Route\n path=\"/\" // url路径\n exact  // bool 严格匹配 ’/link’与’/’是不匹配的，但是在false的情况下它们是匹配的\n component={IndexPage} // 渲染的组件\n/>\n\n<Route\n path=\"/\" // url路径\n exact  // bool 严格匹配 ’/link’与’/’是不匹配的，但是在false的情况下它们是匹配的\n render={() => <div>Home</div>} // 内联渲染\n/>\n```\n\n> <Route> 渲染一些内容有以下三种方式：component / render / children\n\n### Route 渲染方式\n\n#### component\n\n指定只有当位置匹配时才会渲染的 `React` 组件，该组件会接收 `route props` 作为属性。\n\n```jsx\nconst User = ({ match }) => {\n  return <h1>Hello {match.params.username}!</h1>\n}\n\n<Route path=\"/user/:username\" component={User} />\n```\n\n当你使用 `component`（而不是 `render` 或 `children`）时，`Router` 将根据指定的组件，使用 `React.createElement` 创建一个新的 `React` 元素。这意味着，如果你向 `component` 提供一个内联函数，那么每次渲染都会创建一个新组件。这将导致现有组件的卸载和新组件的安装，而不是仅仅更新现有组件。当使用内联函数进行内联渲染时，请使用 `render` 或 `children`（见下文）。\n\n#### render: func\n\n使用 `render` 可以方便地进行内联渲染和包装，而无需进行上文解释的不必要的组件重装。\n\n你可以传入一个函数，以在位置匹配时调用，而不是使用 `component` 创建一个新的 `React` 元素。`render` 渲染方式接收所有与 `component` 方式相同的 `route props`。\n\n```jsx\n// 方便的内联渲染\n<Route path=\"/home\" render={() => <div>Home</div>} />\n\n// 包装\nconst FadingRoute = ({ component: Component, ...rest }) => (\n  <Route {...rest} render={props => (\n    <FadeIn>\n      <Component {...props} />\n    </FadeIn>\n  )} />\n)\n\n<FadingRoute path=\"/cool\" component={Something} />\n```\n\n> 警告：<Route component> 优先于 <Route render>，因此不要在同一个 <Route> 中同时使用两者。\n\n#### children: func\n\n有时候不论 `path` 是否匹配位置，你都想渲染一些内容。在这种情况下，你可以使用 `children` 属性。除了不论是否匹配它都会被调用以外，它的工作原理与 `render` 完全一样。\n\n`children` 渲染方式接收所有与 `component` 和 `render` 方式相同的 `route props`，除非路由与 `URL` 不匹配，不匹配时 `match` 为 `null`。这允许你可以根据路由是否匹配动态地调整用户界面。如下所示，如果路由匹配，我们将添加一个激活类：\n\n```jsx\nconst ListItemLink = ({ to, ...rest }) => (\n  <Route path={to} children={({ match }) => (\n    <li className={match ? \'active\' : \'\'}>\n      <Link to={to} {...rest} />\n    </li>\n  )} />\n)\n\n<ul>\n  <ListItemLink to=\"/somewhere\" />\n  <ListItemLink to=\"/somewhere-else\" />\n</ul>\n```\n\n这对动画也很有用：\n\n```jsx\n<Route children={({ match, ...rest }) => (\n  {/* Animate 将始终渲染，因此你可以利用生命周期来为其子元素添加进出动画 */}\n  <Animate>\n    {match && <Something {...rest} />}\n  </Animate>\n)} />\n```\n\n> 警告：`<Route component>` 和`<Route render>` 优先于 `<Route children>`，因此不要在同一个 `<Route>` 中同时使用多个。\n\n### Route props\n\n三种渲染方式都将提供相同的三个路由属性：\n\n- `match`\n- `location`\n- `history`\n\n#### match: object\n\n```jsx\nconst Topics = ({ match }) => (\n  <div>\n    <Link to={`${match.url}/rendering`}>Rendering with React</Link>\n    <Route path={`${match.url}/:topicId`} component={Topic} />\n  </div>\n)\n```\n\n**match 对象包含了 `<Route path>` 如何与 URL 匹配的信息，具有以下属性：**\n\n- `params`: object 路径参数，通过解析 URL 中的动态部分获得键值对\n- `isExact`: bool 为 true 时，整个 URL 都需要匹配\n- `path`: string 用来匹配的路径模式，用于创建嵌套的 <Route>\n- `url`: string URL 匹配的部分，用于嵌套的 <Link>\n\n在以下情境中可以获取 match 对象\n\n- 在 Route component 中，以 `this.props.match` 获取\n- 在 Route render 中，以 `({match}) => ()` 方式获取\n- 在 Route children 中，以 `({match}) => ()` 方式获取 uter 中，以 `this.props.match` 的方式获取 `matchPath` 的返回值\n\n#### location: object\n\n`location` 是指你当前的位置，将要去的位置，或是之前所在的位置\n\n```jsx\n{\n  key: \'ac3df4\', // not with HashHistory!\n  pathname: \'/somewhere\'\n  search: \'?some=search-string\',\n  hash: \'#howdy\',\n  state: {\n    [userDefined]: true\n  }\n}\n```\n\n在以下情境中可以获取 `location` 对象\n\n- 在`Route component` 中，以 this.props.location 获取\n- 在 `Route render` 中，以 ({location}) => () 方式获取\n- 在 `Route children` 中，以 ({location}) => () 方式获取\n- 在 `withRouter` 中，以 this.props.location 的方式获取\n\n可以在不同情境中使用 location：\n\n```jsx\n<Link to={location} />\n<NaviveLink to={location} />\n<Redirect to={location />\nhistory.push(location)\nhistory.replace(location)\n```\n\n#### history: object\n\nhistory 对象通常具有以下属性和方法：\n\n- `length`: number 浏览历史堆栈中的条目数\n- `action`: string 路由跳转到当前页面执行的动作，分为 PUSH, REPLACE, POP\n- `location`: object 当前访问地址信息组成的对象\n- `push(path, [state])` 在历史堆栈信息里加入一个新条目。\n- `replace(path, [state])` 在历史堆栈信息里替换掉当前的条目\n- `go(n)` 将 history 堆栈中的指针向前移动 n。\n- `goBack()` 等同于 go(-1)\n- `goForward` 等同于 go(1)\n- `block(prompt)` 阻止跳转\n\n### 属性\n\n#### path: string\n\n可以是 path-to-regexp 能够理解的任何有效的 URL 路径。\n\n```jsx\n<Route path=\"/users/:id\" component={User} />\n```\n\n没有定义 `path` 的 `<Route>` 总是会被匹配。【常用于搭建 404 页面】\n\n#### exact: bool\n\n如果为 true，则只有在 path 完全匹配 location.pathname 时才匹配。\n\n```jsx\n<Route exact path=\"/one\" component={OneComponent} />\n// url: /one/two 不可以匹配到 OneComponent\n// 若是没加 exact， 则路径为 /one/two 或 /one/two... 都可以匹配到该组件\n```\n\n#### strict: bool\n\n如果为 `true`，则具有尾部斜杠的 `path` 仅与具有尾部斜杠的 `location.pathname` 匹配。当 `location.pathname` 中有附加的 `URL` 片段时，`strict` 就没有效果了。\n\n```jsx\n<Route strict path=\"/one/\" component={OneComponent} />\n// url /one 不匹配\n// url /one/ 或 /one/two 匹配\n```\n\n> 警告：可以使用 `strict` 来强制规定`location.pathname` 不能具有尾部斜杠，但是为了做到这一点，`strict` 和 `exact` 必须都是 `true`。\n\n#### sensitive: bool\n\n如果为 `true`，进行匹配时将区分大小写。\n\n## 组件-Redirect\n\n使用 `<Redirect>` 会导航到一个新的位置。新的位置将覆盖历史堆栈中的当前条目，例如服务器端重定向`（HTTP 3xx）`。\n\n```jsx\nimport { Route, Redirect } from \'react-router-dom\'\n\n<Route\n  exact\n  path=\"/\"\n  render={() => (loggedIn ? <Redirect to=\"/dashboard\" /> : <PublicHomePage />)}\n/>\n```\n\n### to: string\n\n要重定向到的 `URL`，可以是 `path-to-regexp` 能够理解的任何有效的 `URL` 路径。所有要使用的 `URL` 参数必须由 `from` 提供。\n\n```jsx\n<Redirect to=\"/somewhere/else\" />\n```\n\n### to: object\n\n要重定向到的位置，其中 `pathname` 可以是 `path-to-regexp` 能够理解的任何有效的 `URL` 路径。\n\n```jsx\n<Redirect\n  to={{\n    pathname: \'/login\',\n    search: \'?utm=your+face\',\n    state: {\n      referrer: currentLocation\n    }\n  }}\n/>\n```\n\n上例中的 `state` 对象可以在重定向到的组件中通过 `this.props.location.state` 进行访问。而 `referrer` 键（不是特殊名称）将通过路径名 `/login` 指向的登录组件中的 `this.props.location.state.referrer`进行访问。\n\n### push: bool\n\n如果为 true，重定向会将新的位置推入历史记录，而不是替换当前条目。\n\n```jsx\n<Redirect push to=\"/somewhere/else\" />\n```\n\n### from: string\n\n要从中进行重定向的路径名，可以是 `path-to-regexp` 能够理解的任何有效的 `URL` 路径。所有匹配的 `URL` 参数都会提供给 `to`，必须包含在 `to` 中用到的所有参数，`to` 未使用的其它参数将被忽略。\n\n只能在 `<Switch>`组件内使用 `<Redirect from>`，以匹配一个位置。\n\n```jsx\n<Switch>\n  <Redirect from=\"/old-path\" to=\"/new-path\" />\n  <Route path=\"/new-path\" component={Place} />\n</Switch>\n```\n\n```jsx\n// 根据匹配参数进行重定向\n<Switch>\n  <Redirect from=\"/users/:id\" to=\"/users/profile/:id\" />\n  <Route path=\"/users/profile/:id\" component={Profile} />\n</Switch>\n```\n\n> 译注：经过实践，发现以上“根据匹配参数进行重定向”的示例存在 bug，没有效果。to 中的 :id 并不会继承 from 中的 :id 匹配的值，而是直接作为字符串显示到浏览器地址栏！！！\n\n### exact: bool\n\n### strict: bool\n\n## 组件-link\n\n```jsx\n// to为string\n<Link to=\'/courses?sort=name\'/>\n\n// to为obj\n<Link to={{\n  pathname: \'/courses\',\n  search: \'?sort=name\',\n  hash: \'#the-hash\',\n  state: { fromDashboard: true }\n}}/>\n\n// replace\n<Link to=\"/courses\" replace />\n// replace(bool)：为 true 时，点击链接后将使用新地址替换掉访问历史记录里面的原地址；\n// 为 false 时，点击链接后将在原有访问历史记录的基础上添加一个新的纪录。默认为 false；\n```\n\n## 组件-NavLink\n\n```jsx\n<NavLink to=\"/about\" exact>About</NavLink>\n\n<NavLink\n  to=\"/faq\"\n  activeClassName=\"selected\"\n>FAQs</NavLink>\n\n<NavLink\n  to=\"/faq\"\n  activeStyle={{\n    fontWeight: \'bold\',\n    color: \'red\'\n   }}\n>FAQs</NavLink>\n\n<NavLink\n  to=\"/events/123\"\n  isActive={oddEvent}\n>Event 123</NavLink>\n```\n\n## 参考\n\n- [React Router 中文文档（一）](https://segmentfault.com/a/1190000014294604#articleHeader2)\n- [react-浅析 react-router4](https://blog.segmentfault.net/a/1190000016610898#articleHeader5)', '2019-02-11 12:39:22', '2019-02-20 05:43:35');
INSERT INTO `article` VALUES (21, 'react-router - 实践系列', '## 简单使用\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Switch, Route, Link, NavLink } from \'react-router-dom\'\n\nconst Home = () => (\n  <div>\n    <h2>主页</h2>\n    <Link to=\"/article/1\">文章1</Link>\n  </div>\n)\n\nconst Mine = () => <h2>我的</h2>\n\nclass Article extends Component {\n  handleClick = () => {\n    // 坑点： this.props.history.push(\'home\') 会跳转到路径 /article/home\n    this.props.history.push(\'/home\')\n  }\n\n  render() {\n    const { match } = this.props\n    return (\n      <div>\n        <h2>文章</h2>\n        <ul>\n          <li>路由传参： {match.params.topicId}</li>\n        </ul>\n        <button onClick={this.handleClick}>go Home</button>\n      </div>\n    )\n  }\n}\n\n/**\n * 坑点1： You should not use <Link> outside a <Router> @desc Link 组件不要在 Router 组件外使用\n * 坑点2： A <Router> may have only one child element @desc Router 组件只能有一个子元素\n * 坑点3： /article/:topicId 匹配不到 /Article 组件， /article/123 可以\n */\nclass App extends Component {\n  render() {\n    return (\n      <div>\n        <Router>\n          <div>\n            <NavLink to=\"/\" activeClassName=\"active\">\n              首页\n            </NavLink>\n            <NavLink to=\"/article\" activeClassName=\"active\">\n              文章\n            </NavLink>\n            <NavLink to=\"/mine\" activeClassName=\"active\">\n              我的\n            </NavLink>\n            <hr />\n            <Switch>\n              <Route exact path=\"/\" component={Home} />\n              <Route path=\"/article/:topicId\" component={Article} />\n              <Route path=\"/mine\" component={Mine} />\n            </Switch>\n          </div>\n        </Router>\n      </div>\n    )\n  }\n}\n\nexport default App\n```\n\n## url 跳转 + 传参\n\n<!--more-->\n\n### 路由跳转\n\n```jsx\n<Route path=\"/article/:topicId\" component={Article} />\n\n// html\n<Link to=\"/article/1\">文章1</Link>\n\n// js 方式\nthis.props.history.push(\'/article/1\')\n\n// Article 组件 接收参数\nthis.props.match.params.topicId\n```\n\n### 通过 query\n\n前提：必须由其他页面跳过来，参数才会被传递过来\n\n```jsx\n// 不需要配置路由表。路由表中的内容照常\n<Route path=\"/article\" component={Article} />\n\n// html\n<Link to={{ pathname: \'/article\', query: { topicId: 2 } }}>文章2</Link>\n\n// js 方式\nthis.props.history.push({ pathname : \'/article\' ,query : { topicId: 2} })\n\n// Article 组件 接收参数\nthis.props.location.query.topicId //建议不用 刷新页面时 丢失\n```\n\n### 通过 state\n\n同 `query` 差不多，只是属性不一样，而且 `state` 传的参数是加密的，`query` 传的参数是公开的，在地址栏\n\n```jsx\n// 不需要配置路由表。路由表中的内容照常\n<Route path=\"/article\" component={Article} />\n\n// html\n<Link to={{ pathname: \'/article\', state: { topicId: 2 } }}>文章2</Link>\n\n// js 方式\nthis.props.history.push({ pathname: \'/article\', state: { topicId: 2 } })\n\n// Article 组件 接收参数\nthis.props.location.state.topicId\n```\n\n## 重定向\n\n```jsx\n// 通过from匹配路由重定向\n<Switch>\n  <Redirect from=\"/users/:id\" to=\"/users/profile/:id\" />\n  <Route path=\"/users/profile/:id\" component={Profile} />\n</Switch>\n\n// 通过render重定向\n<Route exact path=\"/\" render={() => (\n  loggedIn ? (\n    <Redirect to=\"/dashboard\"/>\n  ) : (\n    <PublicHomePage/>\n  )\n)}/>\n```\n\n### Redirect(Auth)\n\n```jsx\nimport React, { Component } from \'react\'\nimport {\n  BrowserRouter as Router,\n  Route,\n  Link,\n  Redirect,\n  withRouter\n} from \'react-router-dom\'\n\nconst fakeAuth = {\n  isAuthenticated: false,\n  authenticate(cb) {\n    this.isAuthenticated = true\n    setTimeout(cb, 100) // fake async\n  },\n  signout(cb) {\n    this.isAuthenticated = false\n    setTimeout(cb, 100)\n  }\n}\n\n/**\n * @class Login - 登录组件\n * 接受参数 this.props.location.state.from\n * 如果登录成功 redirectToReferrer = true, 则跳转回之前的页面 <Redirect to={from} />\n */\nclass Login extends Component {\n  state = { redirectToReferrer: false }\n\n  login = () => {\n    fakeAuth.authenticate(() => {\n      this.setState({ redirectToReferrer: true })\n    })\n  }\n\n  render() {\n    let { from } = this.props.location.state || { from: { pathname: \'/\' } }\n    let { redirectToReferrer } = this.state\n\n    if (redirectToReferrer) return <Redirect to={from} />\n\n    return (\n      <div>\n        <p>You must log in to view the page at {from.pathname}</p>\n        <button onClick={this.login}>Log in</button>\n      </div>\n    )\n  }\n}\n\n/**\n * @class AuthRoute - 权限高阶路由组件\n * 通过 fakeAuth.isAuthenticated 控制是否重定向到 /login\n * 传递参数 { from: props.location }\n */\nconst AuthRoute = ({ component: Component, ...rest }) => (\n  <Route\n    {...rest}\n    render={props =>\n      fakeAuth.isAuthenticated ? (\n        <Component {...props} />\n      ) : (\n        <Redirect\n          to={{\n            pathname: \'/login\',\n            state: { from: props.location }\n          }}\n        />\n      )\n    }\n  />\n)\n\n/**\n * @func AuthStatus - 显示登录状态的组件\n * @desc 通过 withRouter 包裹，获得 this.props.history 用于跳转\n *       登录成功，显示 login succeeds，并显示注销按钮\n *       未登录，显示 You are not logged in.\n */\nconst AuthStatus = withRouter(\n  ({ history }) =>\n    fakeAuth.isAuthenticated ? (\n      <p>\n        login succeeds\n        <button\n          onClick={() => {\n            fakeAuth.signout(() => history.push(\'/\'))\n          }}>\n          Sign out\n        </button>\n      </p>\n    ) : (\n      <p>You are not logged in.</p>\n    )\n)\n\nclass App extends Component {\n  render() {\n    return (\n      <Router>\n        <div>\n          <Link to=\"/auth\">Auth Page</Link>\n          <AuthStatus />\n          <Route path=\"/login\" component={Login} />\n          <AuthRoute path=\"/auth\" component={() => <h2>Auth page</h2>} />\n        </div>\n      </Router>\n    )\n  }\n}\n\nexport default App\n```\n\n## NotFound\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Route, Switch } from \'react-router-dom\'\n\nclass App extends Component {\n  render() {\n    return (\n      <Router>\n        <Switch>\n          <Route exact path=\"/\" component={() => <h2>Home</h2>} />\n          <Route component={() => <h2>404, not found</h2>} />\n        </Switch>\n      </Router>\n    )\n  }\n}\nexport default App\n```\n\n## 何时使用 Switch\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Route } from \'react-router-dom\'\n\n// 输入地址 /article 可以发现 两个组件同时都被命中，这是我们不希望出现的\n// 这个时候可以使用Switch，他只会命中第一个命中的路由\nclass App extends Component {\n  render() {\n    return (\n      <Router>\n        <div>\n          <Route path=\"/article\" component={() => <p>article</p>} />\n          <Route path=\"/:name\" component={() => <p>:name</p>} />\n        </div>\n      </Router>\n    )\n  }\n}\nexport default App\n```\n\n## 实现 sidebar\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Route, Link } from \'react-router-dom\'\n\n// 路由表\nconst routes = [\n  {\n    path: \'/\',\n    exact: true,\n    component: () => <h1>Home</h1>\n  },\n  {\n    path: \'/bubblegum\',\n    component: () => <h1>bubblegum</h1>\n  },\n  {\n    path: \'/shoelaces\',\n    component: () => <h1>shoelaces</h1>\n  }\n]\n\nclass App extends Component {\n  render() {\n    const sideBarStyle = {\n      padding: \'10px\',\n      width: \'40%\',\n      background: \'#f0f0f0\'\n    }\n    return (\n      <Router>\n        <div style={{ display: \'flex\' }}>\n          <div style={sideBarStyle}>\n            <ul>\n              <li>\n                <Link to=\"/\">Home</Link>\n              </li>\n              <li>\n                <Link to=\"/bubblegum\">Bubblegum</Link>\n              </li>\n              <li>\n                <Link to=\"/shoelaces\">Shoelaces</Link>\n              </li>\n            </ul>\n          </div>\n          <div style={{ flex: 1, padding: \'10px\' }}>\n            {routes.map((route, index) => (\n              <Route key={index} {...route} />\n            ))}\n          </div>\n        </div>\n      </Router>\n    )\n  }\n}\n\nexport default App\n```\n\n## route config - 定义路由表，实现子路由\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Route, Link } from \'react-router-dom\'\n\n// 路由表\nconst routes = [\n  {\n    path: \'/sandwiches\',\n    component: () => <h2>Sandwiches</h2>\n  },\n  {\n    path: \'/tacos\',\n    component: Tacos,\n    routes: [\n      {\n        path: \'/tacos/bus\',\n        component: () => <h3>sub Bus</h3>\n      },\n      {\n        path: \'/tacos/cart\',\n        component: () => <h3>sub cart</h3>\n      }\n    ]\n  }\n]\n\nfunction Tacos({ routes }) {\n  return (\n    <div>\n      <h2>Tacos</h2>\n      <ul>\n        <li>\n          <Link to=\"/tacos/bus\">Bus</Link>\n        </li>\n        <li>\n          <Link to=\"/tacos/cart\">Cart</Link>\n        </li>\n      </ul>\n\n      {routes.map((route, i) => (\n        <RouteWithSubRoutes key={i} {...route} />\n      ))}\n    </div>\n  )\n}\n\nconst RouteWithSubRoutes = route => (\n  <Route\n    path={route.path}\n    render={props => <route.component {...props} routes={route.routes} />}\n  />\n)\n\nclass App extends Component {\n  render() {\n    return (\n      <Router>\n        <div>\n          <ul>\n            <li>\n              <Link to=\"/tacos\">Tacos</Link>\n            </li>\n            <li>\n              <Link to=\"/sandwiches\">Sandwiches</Link>\n            </li>\n          </ul>\n          <hr />\n          {routes.map((route, i) => (\n            <RouteWithSubRoutes key={i} {...route} />\n          ))}\n        </div>\n      </Router>\n    )\n  }\n}\n\nexport default App\n```\n\n## render', '2019-02-11 12:39:41', '2019-02-20 05:43:22');
INSERT INTO `article` VALUES (22, 'react-router - 动态路由', '## 动态组件\n\n### webpack 的 import 方法\n\n`webpack` 将 `import()`看做一个分割点并将其请求的 `module` 打包为一个独立的 `chunk`。`import()`以模块名称作为参数名并且返回一个 `Promise` 对象。\n\n### 采用适配器模式封装 import()\n\n> 适配器模式（Adapter）:将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。\n\n当前场景，需要解决的是，使用 `import()`异步加载组件后，如何将加载的组件交给 `React` 进行更新。\n方法也很容易，就是利用 `state`。当异步加载好组件后，调用 `setState` 方法，就可以通知到。\n那么，依照这个思路，新建个高阶组件，运用适配器模式，来对 `import()`进行封装。\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Route, Switch } from \'react-router-dom\'\n\nfunction asyncComponent(importComponent) {\n  class AsyncComponent extends Component {\n    constructor(props) {\n      super(props)\n      this.state = {\n        component: null\n      }\n    }\n    async componentDidMount() {\n      const { default: component } = await importComponent()\n      this.setState({\n        component: component\n      })\n    }\n    render() {\n      const RenderComponet = this.state.component\n      return RenderComponet ? <RenderComponet {...this.props} /> : <div>loading...</div>\n    }\n  }\n  return AsyncComponent\n}\n\nconst NoFound = asyncComponent(() => import(\'./components/NoFound\'))\n\nclass App extends Component {\n  render() {\n    return (\n      <Router>\n        <Switch>\n          <Route exact path=\"/\" component={() => <h2>Home</h2>} />\n          <Route component={NoFound} />\n        </Switch>\n      </Router>\n    )\n  }\n}\nexport default App\n```\n<!--more-->\n\n## 基于 react-loadable 实现代码分割\n\nreact-router v4 发生了巨大改变，由静态路由开始转向动态路由，从此，就像使用普通组件一样来声明路由。其实，路由从此就是一个普通组件。\n\n路由的按需加载的实质是代码分割,`react-router` 官网有个代码拆分的示例，是基于 ~~bundle-loader~~ 实现的.现在官网的代码已经改为基于`react-loadable` 实现。\n\n按照上述方法，已经实现了代码分割。然而，`react-loadable` 为我们提供了更好的解决方案。`react-loadable` 核心实现原理也是通过 `dynamic import` 来实现组件的异步加载。在此基础上，它提供了更为强大的功能，如根据延迟时间确定是否显示 `loading` 组件、预加载、支持服务端渲染等。\n\n在另一篇译文- [[译] react-router Code Splitting](https://gershonv.github.io/2018/11/07/react-router-3/)中，我也写过了 react-loadable 的用法介绍，详情可以查看这篇文章。\n\n```jsx\nimport React, { Component } from \'react\'\nimport { BrowserRouter as Router, Route, Switch } from \'react-router-dom\'\nimport Loadable from \'react-loadable\'\n\nconst NoFound = Loadable({\n  loader: () => import(\'./components/NoFound\'),\n  loading: <div>loading</div>\n})\n\nclass App extends Component {\n  render() {\n    return (\n      <Router>\n        <Switch>\n          <Route exact path=\"/\" component={() => <h2>Home</h2>} />\n          <Route component={NoFound} />\n        </Switch>\n      </Router>\n    )\n  }\n}\nexport default App\n```\n\n## 参考\n\n- [React router 动态加载组件-适配器模式的应用](https://blog.segmentfault.net/a/1190000016362502#articleHeader1)\n- [react 扬帆起航之路由配置与组件分割](https://blog.segmentfault.net/a/1190000013589728)', '2019-02-11 12:39:59', '2019-02-20 05:43:01');
INSERT INTO `article` VALUES (23, 'Sequelize - quick start', '在 `Node.js` 社区中，`sequelize` 是一个广泛使用的 `ORM` 框架，它支持 `MySQL`、`PostgreSQL`、`SQLite` 和 `MSSQL` 等多个数据源。\n\n> 有数据库基础或者使用过 `ORM` 操作数据库的经验会更容易上手哦，笔者这里用的以 `mysql` 为主\n\n## 安装\n\n```npm\nnpm i sequelize mysql2 --registry=https://registry.npm.taobao.org\n```\n\n记得提前启动 `mysql` 数据库，创建本例中使用的 `demo` 数据库\n\n```js\nmysql.server start // mac (windows net start mysql)\n\nmysql -uroot -p\n\nCREATE DATABASE IF NOT EXISTS demo;\n```\n<!--more-->\n\n## 建立连接\n\n`Sequelize` 将在初始化时设置连接池，所以如果从单个进程连接到数据库，你最好每个数据库只创建一个实例。 如果要从多个进程连接到数据库，则必须为每个进程创建一个实例，但每个实例应具有“最大连接池大小除以实例数”的最大连接池大小。\n因此，如果您希望最大连接池大小为 90，并且有 3 个工作进程，则每个进程的实例应具有 30 的最大连接池大小。\n\n```js\nconst sequelize = new Sequelize(\'database\', \'username\', \'password\', {\n  host: \'localhost\',\n  dialect: \'mysql\' | \'mariadb\' | \'sqlite\' | \'postgres\' | \'mssql\',\n\n  pool: {\n    max: 5,\n    min: 0,\n    idle: 10000\n  },\n\n  // 仅 SQLite 适用\n  storage: \'path/to/database.sqlite\'\n})\n\n// 或者可以简单的使用一个连接 uri\nconst sequelize = new Sequelize(\'postgres://user:pass@example.com:5432/dbname\')\n```\n\n## 测试连接\n\n您可以使用 `.authenticate()` 函数来测试连接。\n\n```js\nsequelize\n  .authenticate()\n  .then(() => {\n    console.log(\'Connection has been established successfully.\')\n  })\n  .catch(err => {\n    console.error(\'Unable to connect to the database:\', err)\n  })\n```\n\n## model\n\n`Sequelize` 使用 `define` 方法定义模型和表之间的映射。大白话就是 我们可以通过 `model` 去建立表, 添加字段约束等。\n\n```js\nconst User = sequelize.define(\'user\', {\n  firstName: Sequelize.STRING\n  lastName: {\n    type: Sequelize.STRING\n  }\n})\n\n// 通过 sync 可以链接模型到数据库中\n// force: true 如果表已经存在，将会丢弃表\n// force 效果： DROP TABLE IF EXISTS `User` => CREATE TABLE IF NOT EXISTS `USER`...\nUser.sync({ force: true }).then(function() {\n  //...\n})\n```\n\n上面的代码执行后我们可以发现 `demo` 数据库中创建了一个 `users` 的表\n\n### model 操作数据库\n\ndemo 就简单创建数据和查询数据吧\n\n> `Sequelize` 使用 `Bluebird promise` 来控制异步控制流程。\n\n- 链式写法\n\n```js\nUser.sync({ force: true }).then(function() {\n  User.create({\n    firstName: \'John\',\n    lastName: \'Hancock\'\n  }).then(user => {\n    console.log(user.firstName, user.lastName) // John Hancock\n    User.findAll().then(users => {\n      console.log(\'you find: \', users[0][\'firstName\'], users[0][\'lastName\']) // you find:  John Hancock\n    })\n  })\n})\n```\n\n- `async/await` 写法\n\n```js\nUser.sync({ force: true }).then(async () => {\n  try {\n    const user = await User.create({ firstName: \'John\', lastName: \'Hancock\' })\n    const users = await User.findAll()\n    console.log(user.firstName, user.lastName) // John Hancock\n    console.log(\'you find: \', users[0][\'firstName\'], users[0][\'lastName\']) // you find:  John Hancock\n  } catch (err) {\n    console.log(err)\n  }\n})\n```\n\n## 完整 demo\n\n```js\nconst Sequelize = require(\'sequelize\')\n\n/**\n * @params (\'database\', \'username\', \'password\', options)\n */\nconst sequelize = new Sequelize(\'demo\', \'root\', \'123456\', {\n  host: \'localhost\', // 连接的 host 地址\n  dialect: \'mysql\', // 连接到 mysql\n  port: 3306, // 数据库服务器端口\n  pool: {\n    max: 5,\n    min: 0,\n    acquire: 30000,\n    idle: 10000\n  }\n})\n\nconst User = sequelize.define(\'user\', {\n  firstName: Sequelize.STRING,\n  lastName: {\n    type: Sequelize.STRING\n  }\n})\n\nsequelize\n  .authenticate()\n  .then(() => {\n    console.log(\'Connection has been established successfully\')\n    User.sync({ force: true }).then(async () => {\n      try {\n        const user = await User.create({ firstName: \'John\', lastName: \'Hancock\' })\n        const users = await User.findAll()\n        console.log(user.firstName, user.lastName) // John Hancock\n        console.log(\'you find: \', users[0][\'firstName\'], users[0][\'lastName\']) // you find:  John Hancock\n      } catch (err) {\n        console.log(err)\n      }\n    })\n  })\n  .catch(err => {\n    console.error(\'Unable to connect to the database:\', err)\n  })\n```\n\n- [sequelize - getting started](http://docs.sequelizejs.com/manual/installation/getting-started.html)\n- [sequelize - 中文版入门](https://github.com/demopark/sequelize-docs-Zh-CN/blob/master/getting-started.md)', '2019-02-11 12:40:42', '2019-02-11 12:40:42');
INSERT INTO `article` VALUES (24, 'Sequelize - model definition', '## Model definition - 模型定义\n\n`Sequelize` 使用 `define` 方法定义模型和表之间的映射，`Sequelize` 将默认添加 `createdAt` 和 `updatedAt` 属性。因此，您将能够知道数据库条目何时进入数据库以及最后一次更新时。\n`model` 定义格式为 `sequelize.define(\'name\', {attributes}, {configuration})：`\n\n```js\nconst User = sequelize.define(\'user\')\nUser.sync({ force: true })\n```\n\n上式代码在数据库中的执行命令为：\n\n```sql\nDROP TABLE IF EXISTS `users`;\n\nCREATE TABLE IF NOT EXISTS `users` (\n  `id` INTEGER NOT NULL auto_increment ,\n  `createdAt` DATETIME NOT NULL,\n  `updatedAt` DATETIME NOT NULL,\n  PRIMARY KEY (`id`)\n ) ENGINE=InnoDB;\n```\n<!--more-->\n\n### base demo\n\n```js\nconst Bar = sequelize.define(\'bar\', {})\n\nconst Foo = sequelize.define(\'foo\', {\n  id: {\n    type: Sequelize.INTEGER,\n    field: \'fooId\', // 存入数据库中的字段，model 中还是使用 id => foo.id\n    autoIncrement: true, // 是否自增\n    primaryKey: true // 是否为主键\n  },\n\n  age: {\n    type: Sequelize.INTEGER,\n    defaultValue: 18, // 默认值\n    allowNull: true, // 是否为空\n    unique: true, // 是否唯一\n    onUpdate: \'NO ACTION\', // 当被引用的键更新时的操作 String - 可选值是：[\'CASCADE\', \'RESTRICT\', \'SET DEFAULT\', \'SET NULL\', \'NO ACTION\']\n    onDelete: \'NO ACTION\', // 当被引用的键删除时的操作 String - 同上\n    // 验证器\n    validate: {\n      isNumeric: true, // 只允许数字\n      max: 100,\n      min: 1,\n      // 自定义验证\n      isEven(value) {\n        if (parseInt(value) % 2 != 0) {\n          throw new Error(\'Only even values are allowed!\')\n          // 我们也在模型的上下文中，所以如果它存在的话,\n          // this.otherField会得到otherField的值。\n        }\n      }\n    },\n\n    // getters 为列自定义一个访问器 使用this.getDataValue(String)时调用的值\n    get() {\n      const age = this.getDataValue(\'age\')\n      // 可以对该列进行操作...\n      return age\n    },\n\n    // setters 为列自定义一个设置器 使用this.setDataValue(String, Value)时调用的值\n    set(value) {\n      const newValue = value + 3\n      this.setDataValue(\'age\', newValue)\n    }\n  },\n\n  uId: {\n    type: Sequelize.INTEGER,\n    references: {\n      model: Bar, // 这是引用另一个模型\n      key: \'id\' // 引用的字段（注意是在数据中存在的字段名）比如引用 foos 表要引用 fooId 而不是 id\n    }\n  }\n})\n\nBar.sync().then(() => {\n  Foo.sync({ force: true }).then(async () => {\n    try {\n      const foo = await Foo.create({ age: 21 }) // 触发 setters\n      const foos = await Foo.findAll() // 触发 getters\n    } catch (err) {\n      console.log(err)\n    }\n  })\n})\n```\n\n上式代码在数据库中的执行命令为：\n\n```sql\nCREATE TABLE IF NOT EXISTS `bars` (\n  `id` INTEGER NOT NULL auto_increment ,\n  `createdAt` DATETIME NOT NULL,\n  `updatedAt` DATETIME NOT NULL,\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB;\n\nDROP TABLE IF EXISTS `foos`;\n\nCREATE TABLE IF NOT EXISTS `foos` (\n  `fooId` INTEGER auto_increment ,\n  `age` INTEGER DEFAULT 18 UNIQUE,\n  `uId` INTEGER,\n  `createdAt` DATETIME NOT NULL,\n  `updatedAt` DATETIME NOT NULL,\n  PRIMARY KEY (`fooId`),\n  FOREIGN KEY (`uId`) REFERENCES `bars` (`id`)\n) ENGINE=InnoDB;\n\nINSERT INTO `foos` (`fooId`,`age`,`createdAt`,`updatedAt`) VALUES \n(DEFAULT,24,\'2019-01-03 07:34:12\',\'2019-01-03 07:34:12\');\n\nSELECT `fooId` AS `id`, `age`, `uId`, `createdAt`, `updatedAt` FROM `foos` AS `foo`;\n```\n\n## Attributes\n\n```js\nconst User = sequelize.define(\'user\', {\n  column: {\n    type: xxx,             // DataType或字符串，表示列的数据类型 【见下文】\n    allowNull: true,       // 是否设置 NOT NULL（非空）约束\n    defaultValue: xxx      // 默认值\n    unique: false,         // 设置为true时，会为列添加唯一约束\n    primaryKey: false,     // 指定是否是主键\n    field: xxx,            // String - 设置在数据库中的字段名。设置后会，Sequelize会将属性名映射到数据库中的不同名称\n    autoIncrement: false,  // 是否自增\n    references: {          // 引用对象\n      model: xxx,          // 如果列引用到另一个表，可以通过这个属性设置模型或字符串。\n      key: \'id\'            // 该列表示到表外键列的引用\n    }, \n    onUpdate: \'NO ACTION\', // 当被引用的键更新时的操作 String - 可选值是：[\'CASCADE\', \'RESTRICT\', \'SET DEFAULT\', \'SET NULL\', \'NO ACTION\']\n    onDelete: \'NO ACTION\', // 当被引用的键删除时的操作，可选值同上\n    get(){},               // 为列自定义一个访问器 使用this.getDataValue(String)时调用的值 【见下文】\n    set(value){},          // 为列自定义一个设置器 使用this.setDataValue(String, Value)时调用的值 【见下文】\n    validate: {}           // 模型每次保存时调用的验证对象。可是validator.js中的验证函数(参见 DAOValidator)、或自定义的验证函数 【见下文】\n  }\n})\n```\n\n## Attributes - DataTypes\n\n以下是 Sequelize 支持的一些数据类型。 有关完整和更新的列表, 参阅 [DataTypes](http://docs.sequelizejs.com/variable/index.html#static-variable-DataTypes).\n\n```js\nSequelize.STRING                      // VARCHAR(255)\nSequelize.STRING(1234)                // VARCHAR(1234)\nSequelize.STRING.BINARY               // VARCHAR BINARY\nSequelize.TEXT                        // TEXT\nSequelize.TEXT(\'tiny\')                // TINYTEXT\n\nSequelize.INTEGER                     // INTEGER\nSequelize.BIGINT                      // BIGINT\nSequelize.BIGINT(11)                  // BIGINT(11)\n\nSequelize.FLOAT                       // FLOAT\nSequelize.FLOAT(11)                   // FLOAT(11)\nSequelize.FLOAT(11, 12)               // FLOAT(11,12)\n\nSequelize.REAL                        // REAL         仅限于PostgreSQL.\nSequelize.REAL(11)                    // REAL(11)     仅限于PostgreSQL.\nSequelize.REAL(11, 12)                // REAL(11,12)  仅限于PostgreSQL.\n\nSequelize.DOUBLE                      // DOUBLE\nSequelize.DOUBLE(11)                  // DOUBLE(11)\nSequelize.DOUBLE(11, 12)              // DOUBLE(11,12)\n\nSequelize.DECIMAL                     // DECIMAL\nSequelize.DECIMAL(10, 2)              // DECIMAL(10,2)\n\nSequelize.DATE                        // DATETIME 针对 mysql / sqlite, TIMESTAMP WITH TIME ZONE 针对 postgres\nSequelize.DATE(6)                     // DATETIME(6) 针对 mysql 5.6.4+. 小数秒支持多达6位精度\nSequelize.DATEONLY                    // DATE 不带时间.\nSequelize.BOOLEAN                     // TINYINT(1)\n\nSequelize.ENUM(\'value 1\', \'value 2\')  // 一个允许具有 “value 1” 和 “value 2” 的 ENUM\nSequelize.ARRAY(Sequelize.TEXT)       // 定义一个数组。 仅限于 PostgreSQL。\nSequelize.ARRAY(Sequelize.ENUM)       // 定义一个 ENUM 数组. 仅限于 PostgreSQL。\n\nSequelize.JSON                        // JSON 列. 仅限于 PostgreSQL, SQLite and MySQL.\nSequelize.JSONB                       // JSONB 列. 仅限于 PostgreSQL .\n\nSequelize.BLOB                        // BLOB (PostgreSQL 二进制)\nSequelize.BLOB(\'tiny\')                // TINYBLOB (PostgreSQL 二进制. 其他参数是 medium 和 long)\n\nSequelize.UUID                        // PostgreSQL 和 SQLite 的 UUID 数据类型, CHAR(36) BINARY 针对于 MySQL (使用默认值: Sequelize.UUIDV1 或 Sequelize.UUIDV4 来让 sequelize 自动生成 ID)\n\nSequelize.CIDR                        // PostgreSQL 的 CIDR 数据类型\nSequelize.INET                        // PostgreSQL 的 INET 数据类型\nSequelize.MACADDR                     // PostgreSQL 的 MACADDR\n\nSequelize.RANGE(Sequelize.INTEGER)    // 定义 int4range 范围. 仅限于 PostgreSQL.\nSequelize.RANGE(Sequelize.BIGINT)     // 定义 int8range 范围. 仅限于 PostgreSQL.\nSequelize.RANGE(Sequelize.DATE)       // 定义 tstzrange 范围. 仅限于 PostgreSQL.\nSequelize.RANGE(Sequelize.DATEONLY)   // 定义 daterange 范围. 仅限于 PostgreSQL.\nSequelize.RANGE(Sequelize.DECIMAL)    // 定义 numrange 范围. 仅限于 PostgreSQL.\n\nSequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // 定义 tstzrange 范围的数组. 仅限于 PostgreSQL.\n\nSequelize.GEOMETRY                    // 空间列.  仅限于 PostgreSQL (具有 PostGIS) 或 MySQL.\nSequelize.GEOMETRY(\'POINT\')           // 具有几何类型的空间列.  仅限于 PostgreSQL (具有 PostGIS) 或 MySQL.\nSequelize.GEOMETRY(\'POINT\', 4326)     // 具有几何类型和SRID的空间列.  仅限于 PostgreSQL (具有 PostGIS) 或 MySQL.\n```\n\n## Attributes - getters/setters\n\n\n可以在模型上定义\'对象属性\' `getter` 和 `setter` 函数，这些可以用于映射到数据库字段的“保护”属性，也可以用于定义“伪”属性。\n\n`Getters` 和 `Setters` 可以通过两种方式定义（您可以混合使用这两种方式）：\n\n- 作为属性定义的一部分\n- 作为模型参数的一部分\n\n> 注意: 如果在两个地方定义了 `getter` 或 `setter`，那么在相关属性定义中找到的函数始终是优先的。\n\n### 定义为属性定义的一部分\n\n```js\nconst Employee = sequelize.define(\'employee\', {\n  name: {\n    type: Sequelize.STRING,\n    allowNull: false,\n    get() {\n      const title = this.getDataValue(\'title\')\n      // \'this\' 允许你访问实例的属性\n      return this.getDataValue(\'name\') + \' (\' + title + \')\'\n    }\n  },\n  title: {\n    type: Sequelize.STRING,\n    allowNull: false,\n    set(val) {\n      this.setDataValue(\'title\', val.toUpperCase())\n    }\n  }\n})\n\nEmployee.sync({ force: true }).then(() => {\n  Employee.create({ name: \'John Doe\', title: \'senior engineer\' }).then(employee => {\n    console.log(employee.get(\'name\')) // John Doe (SENIOR ENGINEER)\n    console.log(employee.get(\'title\')) // SENIOR ENGINEER\n  })\n})\n```\n\n### 定义为模型参数的一部分\n\n以下是在模型参数中定义 `getter` 和 `setter` 的示例。\n`fullName getter`，是一个说明如何在模型上定义伪属性的例子 - 这些属性实际上不是数据库模式的一部分。 事实上，伪属性可以通过两种方式定义：使用模型 `getter`，或者使用虚拟数据类型的列。 虚拟数据类型可以有验证，而虚拟属性的 `getter` 则不能。\n\n请注意，`fullName getter` 函数中引用的 `this.firstname` 和 `this.lastname` 将触发对相应 `getter` 函数的调用。 如果你不想那样使用`getDataValue()`方法来访问原始值（见下文）。\n\n```js\nconst Foo = sequelize.define(\n  \'foo\',\n  {\n    firstname: Sequelize.STRING,\n    lastname: Sequelize.STRING\n  },\n  {\n    getterMethods: {\n      fullName() {\n        return this.firstname + \' \' + this.lastname\n      }\n    },\n\n    setterMethods: {\n      fullName(value) {\n        const names = value.split(\' \')\n        this.setDataValue(\'firstname\', names.slice(0, -1).join(\' \'))\n        this.setDataValue(\'lastname\', names.slice(-1).join(\' \'))\n      }\n    }\n  }\n)\n\nFoo.sync({ force: true }).then(async () => {\n  try {\n    await Foo.create({ firstname: \'guo\', lastname: \'dada\' })\n    const Foos = await Foo.findAll()\n    console.log(Foos[0][\'fullName\']) // guo dada\n  } catch (err) {\n    console.log(err)\n  }\n})\n```\n\n## Attributes - Validations\n\n模型验证，允许您为模型的每个属性指定格式/内容/继承验证。\n\n验证会自动运行在 `create` ， `update` 和 `save` 上。 你也可以调用 `validate()` 手动验证一个实例。\n\n验证由 [validator.js](https://github.com/chriso/validator.js) 实现。\n\n```js\nconst ValidateMe = sequelize.define(\'foo\', {\n  foo: {\n    type: Sequelize.STRING,\n    validate: {\n      is: [\"^[a-z]+$\",\'i\'],     // 只允许字母\n      is: /^[a-z]+$/i,          // 与上一个示例相同,使用了真正的正则表达式\n      not: [\"[a-z]\",\'i\'],       // 不允许字母\n      isEmail: true,            // 检查邮件格式 (foo@bar.com)\n      isUrl: true,              // 检查连接格式 (http://foo.com)\n      isIP: true,               // 检查 IPv4 (129.89.23.1) 或 IPv6 格式\n      isIPv4: true,             // 检查 IPv4 (129.89.23.1) 格式\n      isIPv6: true,             // 检查 IPv6 格式\n      isAlpha: true,            // 只允许字母\n      isAlphanumeric: true,     // 只允许使用字母数字\n      isNumeric: true,          // 只允许数字\n      isInt: true,              // 检查是否为有效整数\n      isFloat: true,            // 检查是否为有效浮点数\n      isDecimal: true,          // 检查是否为任意数字\n      isLowercase: true,        // 检查是否为小写\n      isUppercase: true,        // 检查是否为大写\n      notNull: true,            // 不允许为空\n      isNull: true,             // 只允许为空\n      notEmpty: true,           // 不允许空字符串\n      equals: \'specific value\', // 只允许一个特定值\n      contains: \'foo\',          // 检查是否包含特定的子字符串\n      notIn: [[\'foo\', \'bar\']],  // 检查是否值不是其中之一\n      isIn: [[\'foo\', \'bar\']],   // 检查是否值是其中之一\n      notContains: \'bar\',       // 不允许包含特定的子字符串\n      len: [2,10],              // 只允许长度在2到10之间的值\n      isUUID: 4,                // 只允许uuids\n      isDate: true,             // 只允许日期字符串\n      isAfter: \"2011-11-05\",    // 只允许在特定日期之后的日期字符串\n      isBefore: \"2011-11-05\",   // 只允许在特定日期之前的日期字符串\n      max: 23,                  // 只允许值 <= 23\n      min: 23,                  // 只允许值 >= 23\n      isCreditCard: true,       // 检查有效的信用卡号码\n\n      // 也可以自定义验证:\n      isEven(value) {\n        if (parseInt(value) % 2 != 0) {\n          throw new Error(\'Only even values are allowed!\')\n          // 我们也在模型的上下文中，所以如果它存在的话, \n          // this.otherField会得到otherField的值。\n        }\n      }\n    }\n  }\n})\n```\n\n请注意，如果需要将多个参数传递给内置的验证函数，则要传递的参数必须位于数组中。 但是，如果要传递单个数组参数，例如isIn的可接受字符串数组，则将被解释为多个字符串参数，而不是一个数组参数。 要解决这个问题，传递一个单一长度的参数数组，比如`[[\'one\'，\'two\']]`。\n\n要使用自定义错误消息而不是 `validator.js` 提供的错误消息，请使用对象而不是纯值或参数数组，例如不需要参数的验证器可以被给定自定义消息:\n\n```js\nisInt: {\n  msg: \"Must be an integer number of pennies\"\n}\n```\n\n或者如果还需要传递参数，请添加一个 `args` 属性：\n\n```js\nisIn: {\n  args: [[\'en\', \'zh\']],\n  msg: \"Must be English or Chinese\"\n}\n```\n\n当使用自定义验证器函数时，错误消息将是抛出的 `Error` 对象所持有的任何消息。\n\n有关内置验证方法的更多详细信息，请参阅[the validator.js project](https://github.com/chriso/validator.js) 。\n\n### 验证器 与 allowNull\n\n如果模型的特定字段设置为允许null（使用 `allowNull：true` ），并且该值已设置为 `null` ，则其验证器不会运行。\n\n这意味着，您可以有一个字符串字段，该字段验证其长度至少为5个字符，但也允许为 `null`。\n\n你可以通过设置 `notNull` 验证器来自定义 `allowNull` 错误消息, 像这样\n\n```js\nconst User = sequelize.define(\'user\', {\n  name: {\n    type: Sequelize.STRING,\n    allowNull: false,\n    validate: {\n      notNull: {\n        msg: \'Please enter your name\'\n      }\n    }\n  }\n})\n```\n\n### 模型验证\n\n验证器也可以在特定字段验证器之后用来定义检查模型。例如，你可以确保纬度和经度都不设置，或者两者都设置，如果设置了一个而另一个未设置则验证失败。\n\n模型验证器方法与模型对象的上下文一起调用，如果它们抛出错误，则认为失败，否则通过。 这与自定义字段特定的验证器一样。\n\n所收集的任何错误消息都将与验证结果对象一起放在字段验证错误中，这个错误使用在 `validate` 参数对象中以失败的验证方法的键来命名。即便在任何一个时刻，每个模型验证方法只能有一个错误消息，它会在数组中显示为单个字符串错误，以最大化与字段错误的一致性。\n\n一个例子:\n\n```js\nconst Pub = Sequelize.define(\'pub\', {\n  name: { type: Sequelize.STRING },\n  address: { type: Sequelize.STRING },\n  latitude: {\n    type: Sequelize.INTEGER,\n    allowNull: true,\n    defaultValue: null,\n    validate: { min: -90, max: 90 }\n  },\n  longitude: {\n    type: Sequelize.INTEGER,\n    allowNull: true,\n    defaultValue: null,\n    validate: { min: -180, max: 180 }\n  },\n}, {\n  validate: {\n    bothCoordsOrNone() {\n      if ((this.latitude === null) !== (this.longitude === null)) {\n        throw new Error(\'Require either both latitude and longitude or neither\')\n      }\n    }\n  }\n})\n```\n\n在这种简单情况下，如果给定纬度或经度，而不是同时包含两者，则验证失败。 如果我们尝试构建一个超范围的纬度和经度，那么 `raging_bullock_arms.validate()` 可能会返回\n\n```js\n{\n  \'latitude\': [\'Invalid number: latitude\'],\n  \'bothCoordsOrNone\': [\'Require either both latitude and longitude or neither\']\n}\n```\n\n## configuration\n\n你还可以修改 `Sequelize` 处理列名称的方式：\n\n```js\nconst Bar = sequelize.define(\n  \'bar\',\n  {\n    /* bla */\n  },\n  {\n    // 不添加时间戳属性 (updatedAt, createdAt)\n    timestamps: false,\n\n    // 不删除数据库条目，但将新添加的属性deletedAt设置为当前日期（删除完成时）。\n    // paranoid 只有在启用时间戳时才能工作\n    paranoid: true,\n\n    // 将自动设置所有属性的字段选项为下划线命名方式。\n    // 不会覆盖已经定义的字段选项\n    underscored: true,\n\n    // 禁用修改表名; 默认情况下，sequelize将自动将所有传递的模型名称（define的第一个参数）转换为复数。 如果你不想这样，请设置以下内容\n    freezeTableName: true,\n\n    // 定义表的名称\n    tableName: \'my_very_custom_table_name\',\n\n    // 启用乐观锁定。 启用时，sequelize将向模型添加版本计数属性，\n    // 并在保存过时的实例时引发OptimisticLockingError错误。\n    // 设置为true或具有要用于启用的属性名称的字符串。\n    version: true\n  }\n)\n```\n\n如果你希望 `sequelize` 处理时间戳，但只想要其中一部分，或者希望您的时间戳被称为别的东西，则可以单独覆盖每个列：\n\n```js\nconst Foo = sequelize.define(\n  \'foo\',\n  {\n    /* bla */\n  },\n  {\n    // 不要忘记启用时间戳！\n    timestamps: true,\n\n    // 我不想要 createdAt\n    createdAt: false,\n\n    // 我想 updateAt 实际上被称为 updateTimestamp\n    updatedAt: \'updateTimestamp\',\n\n    // 并且希望 deletedAt 被称为 destroyTime（请记住启用paranoid以使其工作）\n    deletedAt: \'destroyTime\',\n    paranoid: true\n  }\n)\n```\n\n您也可以更改数据库引擎，例如 变更到到 `MyISAM`, 默认值是 `InnoDB`。\n\n```js\nconst Person = sequelize.define(\'person\', { /* attributes */ }, {\n  engine: \'MYISAM\'\n})\n\n// 或全局的\nconst sequelize = new Sequelize(db, user, pw, {\n  define: { engine: \'MYISAM\' }\n})\n```\n\n最后，您可以为 `MySQL` 和 `PG` 中的表指定注释\n\n```js\nconst Person = sequelize.define(\'person\', { /* attributes */ }, {\n  comment: \"I\'m a table comment!\"\n})\n```\n\n## 数据库同步\n\n\n当开始一个新的项目时，你还不会有一个数据库结构，并且使用 `Sequelize` 你也不需要它。 只需指定您的模型结构，并让库完成其余操作。 目前支持的是创建和删除表：\n\n```js\n// 创建表:\nProject.sync()\nTask.sync()\n\n// 强制创建!\nProject.sync({force: true}) // 这将先丢弃表，然后重新创建它\n\n// 删除表:\nProject.drop()\nTask.drop()\n\n// 事件处理:\nProject.[sync|drop]().then(() => {\n  // 好吧...一切都很好！\n}).catch(error => {\n  // oooh，你输入了错误的数据库凭据？\n})\n```\n\n因为同步和删除所有的表可能要写很多行，你也可以让 `Sequelize` 来为做这些：\n\n```js\n// 同步所有尚未在数据库中的模型\nsequelize.sync()\n\n// 强制同步所有模型\nsequelize.sync({force: true})\n\n// 删除所有表\nsequelize.drop()\n\n// 广播处理:\nsequelize.[sync|drop]().then(() => {\n  // woot woot\n}).catch(error => {\n  // whooops\n})\n```\n\n因为 `.sync({ force: true })` 是具有破坏性的操作，可以使用 `match` 参数作为附加的安全检查。\n\n`match` 参数可以通知 `Sequelize`，以便在同步之前匹配正则表达式与数据库名称 - 在测试中使用 `force：true` 但不使用实时代码的情况下的安全检查。\n\n```js\n// 只有当数据库名称以\'_test\'结尾时，才会运行.sync（）\nsequelize.sync({ force: true, match: /_test$/ });\n```\n\n## sequelize.import\n\n您还可以使用 `import` 方法将模型定义存储在单个文件中。 返回的对象与导入文件的功能中定义的完全相同。\n\n例如 `models/author.js`:\n\n```js\nmodule.exports = (sequelize, DataTypes) => {\n  return sequelize.define(\'author\', {\n    username: DataTypes.STRING(50)\n  })\n}\n```\n\n`app.js`\n\n```js\nconst AuthorModel = sequelize.import(\'./models/author.js\')\n\nAuthorModel.sync({ force: true }).then(async () => {\n  try {\n    const author = AuthorModel.findById(1)\n    console.log(author)\n  } catch (err) {\n    console.log(err)\n  }\n})\n```\n\n### 同时导入多个 model\n\n再建立多一个 model `models/article.js`\n\n```js\nmodule.exports = (sequelize, DataTypes) => {\n  return sequelize.define(\'article\', {\n    title: DataTypes.STRING(50),\n    content: DataTypes.STRING,\n    from: {\n      type: DataTypes.INTEGER,\n      references: {\n        model: \'authors\',\n        key: \'id\'\n      }\n    }\n  })\n}\n```\n\n`app.js`:\n\n```js\nconst fs = require(\'fs\')\nconst path = require(\'path\')\nconst Sequelize = require(\'sequelize\')\n\nconst MODELS_PATH = path.join(__dirname, \'models\')\n\nfs.readdirSync(MODELS_PATH).forEach(file => {\n  sequelize.import(path.join(MODELS_PATH, file))\n})\n\nsequelize.sync().then(() => {\n  const { author: AuthorModel, article } = sequelize.models\n  AuthorModel.create({ username: \'guodada\' }).then(author => {\n    console.log(author.username) // guodada\n  })\n})\n```\n\n## 扩展模型\n\n`Sequelize` 模型是ES6类。 您可以轻松添加自定义实例或类级别的方法。\n\n```js\nconst User = sequelize.define(\'user\', { firstname: Sequelize.STRING })\n\n// 添加一个类级别的方法\nUser.classLevelMethod = function() {\n  return \'foo\'\n}\n\n// 添加实例级别方法\nUser.prototype.instanceLevelMethod = function() {\n  return \'bar\'\n}\n```\n\n当然，您还可以访问实例的数据并生成虚拟的 getter:\n\n```js\nconst User = sequelize.define(\'user\', { firstname: Sequelize.STRING, lastname: Sequelize.STRING })\n\nUser.prototype.getFullname = function() {\n  return [this.firstname, this.lastname].join(\' \')\n}\n\n// 例子:\nUser.build({ firstname: \'foo\', lastname: \'bar\' }).getFullname() // \'foo bar\'\n```\n\n## 索引\n\n`Sequelize` 支持在 `Model.sync()` 或 `sequelize.sync` 中创建的模型定义中添加索引。\n\n```js\nsequelize.define(\n  \'user\',\n  {},\n  {\n    indexes: [\n      // 在 email 上创建一个唯一索引\n      {\n        unique: true,\n        fields: [\'email\']\n      },\n\n      // 在使用 jsonb_path_ops 的 operator 数据上创建一个 gin 索引\n      {\n        fields: [\'data\'],\n        using: \'gin\',\n        operator: \'jsonb_path_ops\'\n      },\n\n      // 默认的索引名将是 [table]_[fields]\n      // 创建多列局部索引\n      {\n        name: \'public_by_author\',\n        fields: [\'author\', \'status\'],\n        where: {\n          status: \'public\'\n        }\n      },\n\n      // 具有有序字段的BTREE索引\n      {\n        name: \'title_index\',\n        method: \'BTREE\',\n        fields: [\'author\', { attribute: \'title\', collate: \'en_US\', order: \'DESC\', length: 5 }]\n      }\n    ]\n  }\n)\n```\n\n## 相关\n\n- [models-definition](http://docs.sequelizejs.com/manual/tutorial/models-definition.html)\n- [models-definition 中文版](https://github.com/demopark/sequelize-docs-Zh-CN/blob/master/models-definition.md)', '2019-02-11 12:41:03', '2019-02-11 12:41:03');
INSERT INTO `article` VALUES (25, 'Sequelize - 使用 model 查询数据', '`Sequelize` 中有两种查询：使用 `Model`（模型）中的方法查询和使用 `sequelize.query()` 进行基于 SQL 语句的原始查询。\n\n<!-- more -->\n\n下面是事先创建好的数据：\n\n```bash\nmysql> select * from users;\n+----+----------+------+------+-------+\n| id | name     | age  | sex  | score |\n+----+----------+------+------+-------+\n|  1 | guodada0 |   15 |    0 |    60 |\n|  2 | guodada1 |   16 |    1 |    80 |\n|  3 | guodada2 |   17 |    0 |    55 |\n|  4 | guodada3 |   18 |    1 |    87 |\n|  5 | guodada4 |   19 |    0 |    73 |\n|  6 | guodada5 |   20 |    1 |    22 |\n+----+----------+------+------+-------+\n6 rows in set (0.00 sec)\n```\n\n定义的 model\n\n```js\nconst UserModel = sequelize.define(\n  \'user\',\n  {\n    name: Sequelize.STRING,\n    age: Sequelize.INTEGER,\n    sex: Sequelize.INTEGER,\n    score: Sequelize.INTEGER\n  },\n  { timestamps: false }\n)\n```\n\n## 查询多项 (findAll)\n\n```js\nconst result = await UserModel.findAll() // result 将是所有 UserModel 实例的数组\n\n// the same as\nconst result = await UserModel.all()\n\n//...\n```\n\n### 限制字段\n\n查询时，如果只需要查询模型的部分属性，可以在通过在查询选项中指定 `attributes` 实现。该选项是一个数组参数，在数组中指定要查询的属性即可，这些要查询的属性就表示要在数据库查询的字段：\n\n```js\nModel.findAll({\n  attributes: [\'foo\', \'bar\']\n})\n```\n\n### 字段重命名\n\n查询属性（字段）可以通过传入一个嵌套数据进行重命名：\n\n```js\nModel.findAll({\n  attributes: [\'foo\', [\'bar\', \'baz\']]\n})\n\n// SELECT foo, bar AS baz ...\n```\n\ndemo\n\n```js\nconst results = await UserModel.findAll({\n  attributes: [[\'name\', \'username\'], \'age\', \'score\']\n})\n\n// [{\"username\":\"guodada0\",\"age\":15,\"score\":60},{\"username\":\"guodada1\",\"age\":16,\"score\":80} ...]\nctx.body = results\n\n// 访问查询结果 通过 instance.get(\'xxx\')\nconsole.log(results[0][\'username\'], results[0].get(\'username\')) // undefind, \'guodada0\'\n```\n\n### 指定筛选条件 (where)\n\n在模型的 `find/finAll` 或 `updates/destroys` 操作中，可以指定一个 `where` 选项以指定筛选条件，\n\n`where` 是一个包含属性/值对对象，sequelize 会根据此对象生产查询语句的筛选条件。\n\n```js\nconst results = await UserModel.findAll({\n  where: {\n    age: 18,\n    name: \'guodada3\'\n  }\n}) //  SELECT * FROM `users` AS `user` WHERE `user`.`age` = 18 AND `user`.`name` = \'guodada3\';\n\nawait UserModel.destroy({\n  where: { name: \'guodada3\' }\n}) // DELETE FROM `users` WHERE name = \'guodada3\'\n\n// ...\n```\n\n#### 复合过滤 / OR / NOT 查询\n\n```js\n$and: {a: 5}           // AND (a = 5)\n$or: [{a: 5}, {a: 6}]  // (a = 5 OR a = 6)\n$gt: 6,                // > 6\n$gte: 6,               // >= 6\n$lt: 10,               // < 10\n$lte: 10,              // <= 10\n$ne: 20,               // != 20\n$not: true,            // IS NOT TRUE\n$between: [6, 10],     // BETWEEN 6 AND 10\n$notBetween: [11, 15], // NOT BETWEEN 11 AND 15\n$in: [1, 2],           // IN [1, 2]\n$notIn: [1, 2],        // NOT IN [1, 2]\n$like: \'%hat\',         // LIKE \'%hat\'\n$notLike: \'%hat\'       // NOT LIKE \'%hat\'\n$iLike: \'%hat\'         // ILIKE \'%hat\' (case insensitive) (PG only)\n$notILike: \'%hat\'      // NOT ILIKE \'%hat\'  (PG only)\n$like: { $any: [\'cat\', \'hat\']}\n                       // LIKE ANY ARRAY[\'cat\', \'hat\'] - also works for iLike and notLike\n$overlap: [1, 2]       // && [1, 2] (PG array overlap operator)\n$contains: [1, 2]      // @> [1, 2] (PG array contains operator)\n$contained: [1, 2]     // <@ [1, 2] (PG array contained by operator)\n$any: [2,3]            // ANY ARRAY[2, 3]::INTEGER (PG only)\n\n$col: \'user.organization_id\' // = \"user\".\"organization_id\", with dialect specific column identifiers, PG in this example\n```\n\n- `$like`: 模糊查询 `%锅` 以 `锅` 结尾的。 `%锅%` 包含 `锅` 的\n- `$in: [10, 11]` - 值为 10 或 11\n\n#### demo\n\n```js\n// SELECT * FROM `users` AS `user` WHERE `user`.`age` > 18 AND `user`.`name` LIKE \'%5\';\nconst results = await UserModel.findAll({\n  where: {\n    age: { $gt: 18 },\n    name: { $like: \'%5\' }\n  }\n})\n\n// SELECT * FROM `users` AS `user` WHERE (`user`.`age` < 1000 OR `user`.`age` IS NULL) AND `user`.`name` LIKE \'%5\';\nconst results = await UserModel.findAll({\n  where: {\n    age: {\n      $in: [15, 20],\n      $or: { $lt: 1000, $eq: null }\n    },\n    name: { $like: \'%5\' }\n  }\n})\n```\n\n### 分页与限制返回结果数\n\n查询进，我们可以使用 `limit` 限制返回结果条数，并可以通过 `offset` 来设置查询偏移（跳过）量，通过这两个属性我们可以实现分页查询的功能：\n\n```js\n// 获取 10 条数据（实例）\nUserModel.findAll({ limit: 10 })\n\n// 跳过 8 条数据（实例）\nUserModel.findAll({ offset: 8 })\n\n// 跳过 5 条数据并获取其后的 5 条数据（实例）\nUserModel.findAll({ offset: 5, limit: 5 })\n```\n\n### 排序\n\n`order` 选项用于查询结果的排序数据。排序时应该传入一个包含属性-排序方向的元组/数组，以保证正确的转义：\n\n```js\nconst result = await UserModel.findAll({\n  order: sequelize.literal(\'name DESC\') // 降序\n})\n\n// demo2\nconst result = await UserModel.findAll({\n  order: [sequelize.literal(\'score DESC\'), sequelize.literal(\'name DESC\')]\n})\n\n// 按 max(age) DESC 排序\n[sequelize.fn(\'max\', sequelize.col(\'age\')), \'DESC\'],\n\n// 按相关联的User 模型的 name 属性排序\n[ArticleModel, \'name\', \'DESC\']\n\n// ...\n```\n\n## 查询单项\n\n```js\n// find\nconst result = await UserModel.find({\n  where: { id: 1 }\n})\nconsole.log(result.name, result.get(\'name\')) // guodada0 guodada0\n\n// findOne\nconst result = await UserModel.findOne({\n  where: { id: 1 }\n})\nconsole.log(result.name, result.get(\'name\')) // guodada0 guodada0\n\n// findById\nconst result = await UserModel.findById(1)\nconsole.log(result.name, result.get(\'name\')) // guodada0 guodada0\n\n// findByPk\nconst result = await UserModel.findByPk(1)\n\n//...\n```\n\n## 查找并创建 (findOrCreate)\n\n`findOrCreate` 可用于检测一个不确定是否存在的元素，如果存在则返回记录，不存在时会使用提供的默认值新建记录。\n\n```js\nUserModel.findOrCreate({\n  where: { name: \'guodada\' },\n  defaults: {\n    age: 23,\n    sex: 1,\n    score: 99\n  }\n}).spread((user, created) => {\n  console.log(user.get(\'name\')) // guodada\n  console.log(created) // 是否创建\n})\n\n// INSERT INTO `users` (`id`,`name`,`age`,`sex`,`score`)\n// VALUES (DEFAULT,\'guodada\',23,1,99);\n```\n\n## 分页查询 (findAndCountAll)\n\n`findAndCountAll` - 结合了 `findAll` 和 `count`\n\n处理程序成功将始终接收具有两个属性的对象：\n\n- `count` - 一个整数，总数记录匹配 `where` 语句和关联的其它过滤器\n- `rows` - 一个数组对象，记录在 `limit` 和 `offset` 范围内匹配 `where` 语句和关联的其它过滤器\n\n```js\nconst result = await UserModel.findAndCountAll({\n  where: {\n    age: {\n      $gte: 18 // 大于等于18\n    }\n  },\n  offset: 1, // 偏移量，可以理解为当前页数\n  limit: 15 // 可以理解为 pageSize , 一页有多少数据\n})\n\n// count 记录数 | row 记录\nconsole.log(result.count, result.rows[0].get())\n\n// SELECT * FROM `users` AS `user` WHERE `user`.`age` >= 18 LIMIT 1, 15;\n```\n\n### 支持 include\n\n它支持 `include`。 只有标记为 `required` 的 `include` 将被添加到计数部分：\n\n假设你想找 `User` 中 发布过 `article` 的记录\n\n```js\nconst UserModel = sequelize.define(\n  \'user\',\n  {\n    name: Sequelize.STRING,\n    age: Sequelize.INTEGER,\n    sex: Sequelize.INTEGER,\n    score: Sequelize.INTEGER\n  },\n  { timestamps: false }\n)\n\nconst ArticleModel = sequelize.define(\'article\', {\n  title: Sequelize.STRING,\n  content: Sequelize.STRING\n})\n\nUserModel.hasMany(ArticleModel) // 关联模型\nArticleModel.belongsTo(UserModel, {\n  constraints: false\n})\n\nconst result = await UserModel.findAndCountAll({\n  include: [{ model: ArticleModel, required: true }],\n  offset: 1,\n  limit: 5\n})\n\nconsole.log(result.count) // 3\n```\n\n`result.row`:\n\n```json\n{\n  \"count\": 3,\n  \"rows\": [\n    {\n      \"id\": 1,\n      \"name\": \"guodada0\",\n      \"age\": 15,\n      \"sex\": 0,\n      \"score\": 60,\n      \"article\": {\n        \"id\": 1,\n        \"title\": \"title1\",\n        \"content\": \"aaa\",\n        \"userId\": 1,\n        \"createdAt\": \"2019-01-07T08:51:13.000Z\",\n        \"updatedAt\": \"2019-01-07T08:51:13.000Z\"\n      }\n    }\n    //...\n  ]\n}\n```\n\n因为 `ArticleModel` 的 `include` 有 `required` 设置，这将导致内部连接，并且只有具有 `ArticleModel` 的用户将被计数。\n如果我们从 `include` 中删除 `required`，那么有和没有 `ArticleModel` 的用户都将被计数。\n在 `include` 中添加一个 `where` 语句会自动使它成为 required：\n\n```js\nconst result = await UserModel.findAndCountAll({\n  include: [{ model: ArticleModel }]\n})\n\nconsole.log(result.count) // 7\n\nconst result = await UserModel.findAndCountAll({\n  include: [{ model: ArticleModel, where: { userId: 2 } }]\n})\n\nconsole.log(result.count) // 2\n```\n\n## 聚合查询\n\n### SQL 中的分组查询\n\n[mysql-聚合函数](https://gershonv.github.io/2018/12/31/mysql-聚合函数/)\n\n`SQL` 查询中，通 `GROUP BY` 语名实现分组查询。GROUP BY 子句要和聚合函数配合使用才能完成分组查询，在 `SELECT` 查询的字段中，如果没有使用聚合函数就必须出现在 ORDER BY 子句中。分组查询后，查询结果为一个或多个列分组后的结果集。\n\n```js\nSELECT 列名, 聚合函数(列名)\nFROM 表名\nWHERE 列名 operator value\nGROUP BY 列名\n[HAVING 条件表达式] [WITH ROLLUP]\n```\n\n在以上语句中：\n\n- 聚合函数 - 分组查询通常要与聚合函数一起使用，聚合函数包括：\n  - `COUNT()`-用于统计记录条数\n  - `SUM()`-用于计算字段的值的总和\n  - `AVG()`-用于计算字段的值的平均值\n  - `MAX`-用于查找查询字段的最大值\n  - `MIX`-用于查找查询字段的最小值\n- `GROUP BY` 子名-用于指定分组的字段\n- `HAVING` 子名-用于过滤分组结果，符合条件表达式的结果将会被显示\n- `WITH ROLLUP` 子名-用于指定追加一条记录，用于汇总前面的数据\n\n### sum(field, [options])\n\n`Sequelize` 提供了聚合函数，可以直接对模型进行聚合查询：\n\n- `aggregate(field, aggregateFunction, [options])`-通过指定的聚合函数进行查询\n- `sum(field, [options])`-求和\n- `count(options: Object)`-统计查询结果数\n- `max(field, [options])`-查询最大值\n- `min(field, [options])`-查询最小值\n\n以上这些聚合函数中，可以通过 `options.attributes`、`options.attributes` 属性指定分组相关字段，并可以通过 `options.having` 指定过滤条件，但没有直接指定 `WITH ROLLUP` 子句的参数。\n\n使用`.sum()`查询订单数量大于 1 的用户订单额：\n\n```js\nconst result = await OrderModel.sum(\'price\', {\n  attributes: [\'name\', [sequelize.fn(\'COUNT\', sequelize.col(\'price\')), \'sum\']],\n  group: \'name\',\n  plain: false, // 执行的查询类型，sequelize会根据这个类型对返回结果格式化。\n  having: {\n    $and: [sequelize.literal(\'COUNT(name) > 1\')]\n  }\n})\n\n// SELECT `name`, SUM(`price`) AS `sum` FROM `orders` AS `order` GROUP BY `name` HAVING (COUNT(name) > 1);\n\n// [ { name: \'guo\', sum: \'44\' }, { name: \'guo2\', sum: \'22\' } ]\n```\n\n- [plain](https://itbilu.com/nodejs/npm/VkYIaRPz-.html#api-instance-fn):执行的查询类型，`sequelize` 会根据这个类型对返回结果格式化\n- [sequelize.literal](https://itbilu.com/nodejs/npm/N1pPjUdMf.html#multi): 创建一个字面量对象，该值不会转义\n\n除直接使用聚合函数外，也可以在 `findAll()`等方法中，指定聚合查询相关参数实现聚合查询。\n查询时，同样可以通过通过 `options.attributes`、`options.attributes` 属性指定分组相关字段，并可以通过 options.having 指定过滤条件。与直接使用聚合函数查询不一样，通过参数构建聚合查询时，\n要以数组或对象形式设置 `options.attributes` 参数中的聚合字段，并需要通过 `sequelize.fn()`方法传入聚合函数。\n\n```js\nconst result = await OrderModel.findAll({\n  attributes: [\'name\', [sequelize.fn(\'SUM\', sequelize.col(\'price\')), \'sum\']],\n  group: \'name\',\n  having: {\n    $and: [sequelize.literal(\'COUNT(name) > 1\')]\n  },\n  raw: true // row 对查询结果进行格式化， false 返回 instance\n})\n```\n\n`sequelize.fn()` - 函数调用\n\n```js\nsequelize.fn(fn, args) -> Sequelize.fn\n```\n\n创建于一个相当于数据库函数的对象。该函数可用于搜索查询的 `where` 和 `order` 部分，以及做为列定义的默认值。如果想在列中引用你定义的函数，就要使用 `sequelize.col`，这样列就能正确的解析，而不是解析为字符串。\n如，将 `username` 字段值解析为大写形式：\n\n```js\ninstance.updateAttributes({\n  username: self.sequelize.fn(\'upper\', self.sequelize.col(\'username\'))\n})\n```\n\n`sequelize.col()` - 列对象\n\n创建一个相当于数据库列的对象。这个方法经常结合 sequelize.fn 使用，它可以保证将列名正确的传递给该方法，而不是经过转义。\n\n### count(options: Object)\n\n```js\nconst result = await OrderModel.count({\n  where: { price: 24 }\n})\n```\n\n### max/min\n\n```js\nconst result = await OrderModel.max(\'price\', {\n  where: {\n    price: { $lt: 23 }\n  }\n})\n```\n\n## 原始查询\n\n[原始查询](https://itbilu.com/nodejs/npm/VJIR1CjMb.html#raw-query)\n\n有时会使用原始查询或执行已准备好的 SQL 语句，这时可以用 `Sequlize` 提供的工具函数 `sequelize.query` 来实现。\n\n```js\nconst result = await sequelize.query(\'SELECT * FROM users\', { model: UserModel })\n```\n\n### 查询参数替换\n\n原始查询中有两种替换查询参数的方法，以:开头的参数的形式替换或以不命名以?替换。在选项对象中传递参数：\n\n- 如果传递一个数组，? 会按数组的顺序被依次替换\n- 巢传递一个对象，:key 将会用对象的键替换。如果对象中未找到指定键，则会引发异常（反之亦然）\n\n```js\nsequelize\n  .query(\'SELECT * FROM projects WHERE status = ?\', { replacements: [\'active\'], type: sequelize.QueryTypes.SELECT })\n  .then(function(projects) {\n    console.log(projects)\n  })\n\nsequelize\n  .query(\'SELECT * FROM projects WHERE status = :status \', {\n    replacements: { status: \'active\' },\n    type: sequelize.QueryTypes.SELECT\n  })\n  .then(function(projects) {\n    console.log(projects)\n  })\n```\n\n### 参数绑定\n\n参数绑定类似于参数替换。尤其是参数替换会在发送到数据库前被 sequelize 转义和替换，而参数绑定会被发送到 SQL 查询文本外。\n\n只有 `SQLite` 和 `PostgreSQL` 支持参数绑定，其它类型数据库都会将其插入到 `SQL` 查询，并以相同的方式进行参数替换。参数绑定可以使用$1、$2……或\\$key 的形式：\n\n- 如果传入的是数组，\\$1 会绑定到数组听第 1 个参数 (bind[0])\n- 如果传入一个对象，$key 会绑定到 `object[\'key\']`。每个 key 必须以非数字的字符开始。$1 不是个有效的 key，尽管 object[\'1\'] 是存在的。\n- 在使用\\$$时，不会被转义而是将$做为一个字面量使用。\n\n传入的数组或对象必须包含所有绑定值，否则 `Sequelize` 会抛出异常。这同样适用于数据库可能会忽略绑定参数的情况下。\n\n数据库可能会做进一步限制，绑定参数不能使用数据库关键字，也不能是表或列名，它在引用文本或数据时也可能被忽略。在 PostgreSQL 中，如果不能从上下文\\$1::varchar 中推断类型，那么也需要进行类型转换\n\n```js\nsequelize\n  .query(\'SELECT *, \"text with literal $$1 and literal $$status\" as t FROM projects WHERE status = $1\', {\n    bind: [\'active\'],\n    type: sequelize.QueryTypes.SELECT\n  })\n  .then(function(projects) {\n    console.log(projects)\n  })\n\nsequelize\n  .query(\'SELECT *, \"text with literal $$1 and literal $$status\" as t FROM projects WHERE status = $status\', {\n    bind: { status: \'active\' },\n    type: sequelize.QueryTypes.SELECT\n  })\n  .then(function(projects) {\n    console.log(projects)\n  })\n```', '2019-02-11 12:41:27', '2019-02-11 12:41:27');
INSERT INTO `article` VALUES (26, 'Sequelize - associations', '本部分描述了 Sequelize 中的各种关联类型。 Sequelize 中有四种类型的关联\n\n- `BelongsTo`\n- `HasOne`\n- `HasMany`\n- `BelongsToMany`\n\n## 基本概念\n\n### Source & Target\n\n我们首先从一个基本概念开始，你将会在大多数关联中使用 `source` 和 `target` 模型。 假设您正试图在两个模型之间添加关联。 这里我们在 `users` 和 `articles` 之间添加一个 `hasOne` 关联。\n\n```js\nconst UserModel = sequelize.define(\n  \'user\',\n  {\n    name: Sequelize.STRING,\n    age: Sequelize.INTEGER\n  },\n  { timestamps: false }\n)\n\nconst ArticleModel = sequelize.define(\'article\', {\n  title: Sequelize.STRING,\n  content: Sequelize.STRING\n})\n\nUserModel.hasOne(ArticleModel)\n```\n\n<!-- more -->\n\n相当于：\n\n```sql\nCREATE TABLE IF NOT EXISTS `users` (\n  `id` INTEGER NOT NULL auto_increment ,\n  `name` VARCHAR(255), `age` INTEGER,\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB;\n\nCREATE TABLE IF NOT EXISTS `articles` (\n  `id` INTEGER NOT NULL auto_increment ,\n  `title` VARCHAR(255),\n  `content` VARCHAR(255),\n  `createdAt` DATETIME NOT NULL,\n  `updatedAt` DATETIME NOT NULL,\n  `userId` INTEGER,\n  PRIMARY KEY (`id`),\n  FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE\n  SET NULL ON UPDATE CASCADE\n) ENGINE=InnoDB;\n```\n\n`UserModel`（函数被调用的模型）是 `source` 。 `ArticleModel` 模型（作为参数传递的模型）是 `target` 。\n\n即 `articles` 表的 `userId` 依赖于 `users` 表的 `id`\n\n此时删除 `users` 表（`source`）, 就会报错了 Cannot drop table \'users\' referenced by a foreign key constraint \'articles_ibfk_1\' on table \'articles\'.\n\n### 外键\n\n当您在模型中创建关联时，会自动创建带约束的外键引用。 下面是设置：\n\n```js\nconst TaskModel = sequelize.define(\'task\', { title: Sequelize.STRING })\nconst UserModel = sequelize.define(\'user\', { name: Sequelize.STRING }, { timestamps: false })\n\nUserModel.hasMany(TaskModel) // 将会添加 userId 到 TaskModel\nTaskModel.belongsTo(UserModel) // 也将会添加 userId 到 TaskModel\n```\n\n将生成以下 SQL：\n\n```sql\nCREATE TABLE IF NOT EXISTS `users` (\n    `id` INTEGER NOT NULL auto_increment,\n    `name` VARCHAR(255),\n    PRIMARY KEY (`id`)\n) ENGINE = InnoDB;\n\nCREATE TABLE IF NOT EXISTS `tasks` (\n    `id` INTEGER NOT NULL auto_increment,\n    `title` VARCHAR(255),\n    `createdAt` DATETIME NOT NULL,\n    `updatedAt` DATETIME NOT NULL,\n    `userId` INTEGER,\n    PRIMARY KEY (`id`),\n    FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE\n    SET NULL ON UPDATE CASCADE\n) ENGINE = InnoDB;\n```\n\n`tasks` 和 `users` 模型之间的关系通过在 `tasks` 表上注入 userId 外键，并将其标记为对 `users` 表的引用。\n默认情况下，如果引用的用户被删除，`userId` 将被设置为 `NULL`，如果更新了 `userId`，则更新 `userId`。 这些选项可以通过将 `onUpdate` 和 `onDelete` 选项传递给关联调用来覆盖。\n验证选项是`RESTRICT`, `CASCADE`, `NO ACTION`, `SET DEFAULT`, `SET NULL`。\n\n对于 `1:1` 和 `1:m` 关联，默认选项是 `SET NULL` 用于删除，`CASCADE` 用于更新。\n对于 `n:m`，两者的默认值是 `CASCADE`。 这意味着，如果您从 `n:m` 关联的一侧删除或更新一行，则引用该行的连接表中的所有行也将被删除或更新。\n\n#### 循环依赖 & 禁用约束\n\n在表之间添加约束意味着当使用 `sequelize.sync` 时，表必须以特定顺序在数据库中创建表。\n如果 `Task` 具有对 `User` 的引用，`users` 表必须在创建 `tasks` 表之前创建。\n这有时会导致循环引用，那么 `sequelize` 将无法找到要同步的顺序。\n想象一下文档和版本的场景。 一个文档可以有多个版本，并且为了方便起见，文档引用了它的当前版本。\n\n```js\nconst Document = sequelize.define(\'document\', { author: Sequelize.STRING }, { timestamps: false })\nconst Version = sequelize.define(\'version\', { timestamp: Sequelize.DATE })\n\nDocument.hasMany(Version) // 这将 documentId 属性添加到 version\nDocument.belongsTo(Version, {\n  as: \'Current\',\n  foreignKey: \'currentVersionId\'\n}) // 这将 currentVersionId 属性添加到 document\n```\n\n但是，上面的代码将导致以下错误: `Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents.`\n\n为了缓解这一点，我们可以向其中一个关联传递 `constraints: false：`\n\n```js\nDocument.hasMany(Version)\nDocument.belongsTo(Version, {\n  as: \'Current\',\n  foreignKey: \'currentVersionId\',\n  constraints: false\n})\n```\n\n这将可以让我们正确地同步表：\n\n```sql\nCREATE TABLE IF NOT EXISTS `documents` (\n    `id` INTEGER NOT NULL auto_increment,\n    `author` VARCHAR(255),\n    `currentVersionId` INTEGER,\n    PRIMARY KEY (`id`)\n) ENGINE = InnoDB;\n\nCREATE TABLE IF NOT EXISTS `versions` (\n    `id` INTEGER NOT NULL auto_increment,\n    `timestamp` DATETIME,\n    `createdAt` DATETIME NOT NULL,\n    `updatedAt` DATETIME NOT NULL,\n    `documentId` INTEGER,\n    PRIMARY KEY (`id`),\n    FOREIGN KEY (`documentId`) REFERENCES `documents` (`id`) ON DELETE\n    SET\n        NULL ON UPDATE CASCADE\n) ENGINE = InnoDB;\n```\n\n#### 无限制地执行外键引用\n\n有时您可能想引用另一个表，而不添加任何约束或关联。 在这种情况下，您可以手动将参考属性添加到您的模式定义中，并标记它们之间的关系。\n\n```js\nconst Trainer = sequelize.define(\'trainer\', {\n  firstName: Sequelize.STRING,\n  lastName: Sequelize.STRING\n})\n\n// Series 将有一个 trainerId = Trainer.id 外参考键\n// 之后我们调用 Trainer.hasMany(series)\nconst Series = sequelize.define(\'series\', {\n  title: Sequelize.STRING,\n  subTitle: Sequelize.STRING,\n  description: Sequelize.TEXT,\n  // 用 `Trainer` 设置外键关系（hasMany）\n  trainerId: {\n    type: Sequelize.INTEGER,\n    references: {\n      model: Trainer,\n      key: \'id\'\n    }\n  }\n})\n\n// Video 将有 seriesId = Series.id 外参考键\n// 之后我们调用 Series.hasOne(Video)\nconst Video = sequelize.define(\'video\', {\n  title: Sequelize.STRING,\n  sequence: Sequelize.INTEGER,\n  description: Sequelize.TEXT,\n  // 用 `Series` 设置关系(hasOne)\n  seriesId: {\n    type: Sequelize.INTEGER,\n    references: {\n      model: Series, // 既可以是表示表名的字符串，也可以是 Sequelize 模型\n      key: \'id\'\n    }\n  }\n})\n\nSeries.hasOne(Video)\nTrainer.hasMany(Series)\n```\n\n## 一对一关联\n\n一对一关联是通过单个外键连接的两个模型之间的关联。\n\n### BelongsTo\n\n`BelongsTo` 关联是在 `source model` 上存在一对一关系的外键的关联。\n\n一个简单的例子是 `Player` 通过 `player` 的外键作为 `Team` 的一部分。\n\n```js\nconst Player = sequelize.define(\'player\', {}, { timestamps: false })\nconst Team = sequelize.define(\'team\', {}, { timestamps: false })\n\nPlayer.belongsTo(Team) // 将向 Player 添加一个 teamId 属性以保存 Team 的主键值\n```\n\n```sql\nCREATE TABLE IF NOT EXISTS `teams` (\n    `id` INTEGER NOT NULL auto_increment,\n    PRIMARY KEY (`id`)\n) ENGINE = InnoDB;\n\nCREATE TABLE IF NOT EXISTS `players` (\n    `id` INTEGER NOT NULL auto_increment,\n    `teamId` INTEGER,\n    PRIMARY KEY (`id`),\n    FOREIGN KEY (`teamId`) REFERENCES `teams` (`id`) ON DELETE\n    SET\n        NULL ON UPDATE CASCADE\n) ENGINE = InnoDB;\n```\n\n#### 外键/目标键\n\n默认情况下，将从目标模型名称和目标主键名称生成 `belongsTo` 关系的外键。\n\n默认的样式是 `camelCase`（小驼峰），但是如果源模型配置为 `underscored: true`（下划线） ，那么将使用字段 `snake_case` 创建 `foreignKey`。\n\n```js\nconst User = sequelize.define(\'user\', {}, { timestamps: false, underscored: true })\nconst Company = sequelize.define(\'company\', {\n  uuid: {\n    type: Sequelize.UUID,\n    primaryKey: true\n  }\n})\n\nUser.belongsTo(Company) // 将用字段 company_uuid 添加 companyUuid 到 user\n```\n\n在已定义 `as` 的情况下，将使用它代替目标模型名称。\n\n```js\nconst User = sequelize.define(\'user\', {}, { timestamps: false })\nconst UserRole = sequelize.define(\'userRole\', {}, { timestamps: false })\n\nUser.belongsTo(UserRole, { as: \'role\' }) // 将 role 添加到 user 而不是 userRole\n```\n\n生成的 `users` 表\n\n```sql\nCREATE TABLE IF NOT EXISTS `users` (\n    `id` INTEGER NOT NULL auto_increment,\n    `roleId` INTEGER,\n    PRIMARY KEY (`id`),\n    FOREIGN KEY (`roleId`) REFERENCES `userRoles` (`id`) ON DELETE\n    SET\n        NULL ON UPDATE CASCADE\n) ENGINE = InnoDB;\n```\n\n在所有情况下，默认外键可以用 `foreignKey` 选项覆盖。 当使用外键选项时，`Sequelize` 将按原样使用：\n\n```js\nconst User = sequelize.define(\'user\', {}, { timestamps: false })\nconst Company = sequelize.define(\'company\', {}, { timestamps: false })\n\nUser.belongsTo(Company, { foreignKey: \'fk_company\' })\n```\n\n目标键\n\n```js\nUser.belongsTo(Company, { foreignKey: \'fk_companyname\', targetKey: \'id\' })\n```\n\n效果：\n\n```js\nconst User = sequelize.define(\n  \'user\',\n  {\n    fk_companyname: {\n      references: {\n        model: Company,\n        key: \'id\'\n      }\n    }\n  },\n  { timestamps: false }\n)\n```\n\n### HasOne\n\n`HasOne` 关联是在 `target model` 上存在一对一关系的外键的关联。\n\n```js\nconst User = sequelize.define(\'user\', {}, { timestamps: false })\nconst Project = sequelize.define(\'project\', {}, { timestamps: false })\n\n// 单向关联\nProject.hasOne(User)\n\n// the same as\nconst User = sequelize.define(\n  \'user\',\n  {\n    projectId: {\n      references: {\n        model: Project,\n        key: \'id\'\n      }\n    }\n  },\n  { timestamps: false }\n)\n```\n\n```js\n// 你也可以定义外键，例如 如果您已经有一个现有的数据库并且想要处理它：\nProject.hasOne(User, { foreignKey: \'initiator_id\' })\n\n// 因为Sequelize将使用模型的名称（define的第一个参数）作为访问器方法，\n// 还可以将特殊选项传递给hasOne：\nProject.hasOne(User, { as: \'Initiator\' })\n\n// 或者让我们来定义一些自己的参考\nconst Person = sequelize.define(\'person\', {})\nPerson.hasOne(Person, { as: \'Father\' }) // 这会将属性 FatherId 添加到 Person\n\n// also possible:\nPerson.hasOne(Person, { as: \'Father\', foreignKey: \'DadId\' }) // 这将把属性 DadId 添加到 Person\n\n// 在这两种情况下，你都可以：\nPerson.setFather\nPerson.getFather\n\n// 如果你需要联结表两次，你可以联结同一张表\nTeam.hasOne(Game, { as: \'HomeTeam\', foreignKey: \'homeTeamId\' })\nTeam.hasOne(Game, { as: \'AwayTeam\', foreignKey: \'awayTeamId\' })\n\nGame.belongsTo(Team)\n```\n\n即使它被称为 `hasOne` 关联，对于大多数 1：1 关系，您通常需要 `BelongsTo` 关联，因为 `BelongsTo` 将会在 `hasOne` 将添加到目标的源上添加 `foreignKey`。\n\n#### 源键\n\n源关键是源模型中的属性，它的目标模型指向外键属性。 默认情况下，hasOne 关系的源键将是源模型的主要属性。 要使用自定义属性，请使用 `sourceKey` 选项。\n\n```js\nconst User = sequelize.define(\'user\', {})\nconst Company = sequelize.define(\'company\', {})\n\n// 将 companyName 属性添加到 User\n// 使用 Company 的 name 属性作为源属性\nCompany.hasOne(User, { foreignKey: \'companyName\', sourceKey: \'name\' })\n```\n\n### HasOne 和 BelongsTo 之间的区别\n\n在 Sequelize `1：1` 关系中可以使用 `HasOne` 和 `BelongsTo` 进行设置。 它们适用于不同的场景。 让我们用一个例子来研究这个差异。\n\n假设我们有两个表可以链接 `Player` 和 `Team` 。 让我们定义他们的模型。\n\n```js\nconst Player = sequelize.define(\'player\', {}, { timestamps: false })\nconst Team = sequelize.define(\'team\', {}, { timestamps: false })\n```\n\n当我们连接 `Sequelize` 中的两个模型时，我们可以将它们称为一对 `source` 和 `target` 模型。像这样\n\n将 **Player** 作为 **source** 而 **Team** 作为 **target**\n\n```js\nPlayer.belongsTo(Team)\n//或\nPlayer.hasOne(Team)\n```\n\n将 **Team** 作为 **source** 而 **Player** 作为 **target**\n\n```js\nTeam.belongsTo(Player)\n//Or\nTeam.hasOne(Player)\n```\n\n`HasOne` 和 `BelongsTo` 将关联键插入到不同的模型中。 `HasOne` 在 `target` 模型中插入关联键，而 `BelongsTo` 将关联键插入到 `source` 模型中。\n\n下是一个示例，说明了 `BelongsTo` 和 `HasOne` 的用法。\n\n```js\nconst Player = sequelize.define(\'player\', {}, { timestamps: false })\nconst Team = sequelize.define(\'team\', {}, { timestamps: false })\nconst Coach = sequelize.define(\'coach\', {}, { timestamps: false })\n\nPlayer.belongsTo(Team) // `teamId` 将被添加到 Player / Source 模型中\nCoach.hasOne(Team) // `coachId` 将被添加到 Team / Target 模型中\n\n// the same as\nconst Player = sequelize.define(\'player\', {\n  teamId: {\n    references: {\n      model: Team,\n      key: \'id\'\n    }\n  }\n})\n\nconst Team = sequelize.define(\'team\', {\n  coachId: {\n    references: {\n      model: Coach,\n      key: \'id\'\n    }\n  }\n})\n```\n\n假设我们的 `Player` 模型有关于其团队的信息为 `teamId` 列。\n关于每个团队的 `Coach` 的信息作为 `coachId` 列存储在 `Team` 模型中。\n这两种情况都需要不同种类的 1：1 关系，因为外键关系每次出现在不同的模型上。\n\n- 当关于关联的信息存在于 `source` 模型中时，我们可以使用 `belongsTo`。 在这种情况下，`Player` 适用于`belongsTo`，因为它具有 `teamId` 列。\n- 当关于关联的信息存在于 `target` 模型中时，我们可以使用 `hasOne`。 在这种情况下， `Coach` 适用于 `hasOne` ，因为 `Team` 模型将其 `Coach` 的信息存储为 `coachId` 字段。\n\n## 一对多关联 (hasMany)\n\n一对多关联将一个来源与多个目标连接起来。 而多个目标接到同一个特定的源。\n\n```js\nconst User = sequelize.define(\'user\', {}, { timestamps: false })\nconst Project = sequelize.define(\'project\', {}, { timestamps: false })\n\n// 好。 现在，事情变得更加复杂（对用户来说并不真实可见）。\n// 首先我们来定义一个 hasMany 关联\nProject.hasMany(User, { as: \'Workers\' })\n```\n\n这会将 `projectId` 属性添加到 `User`。 根据您强调的设置，表中的列将被称为 `projectId` 或 `project_id`。 `Project` 的实例将获得访问器 `getWorkers` 和 `setWorkers`。\n\n有时您可能需要在不同的列上关联记录，您可以使用 `sourceKey` 选项：\n\n```js\nconst City = sequelize.define(\'city\', { countryCode: Sequelize.STRING })\nconst Country = sequelize.define(\'country\', { isoCode: Sequelize.STRING })\n\n// 在这里，我们可以根据国家代码连接国家和城市\nCountry.hasMany(City, { foreignKey: \'countryCode\', sourceKey: \'isoCode\' })\nCity.belongsTo(Country, { foreignKey: \'countryCode\', targetKey: \'isoCode\' })\n```\n\n到目前为止，我们解决了单向关联。 但我们想要更多！ 让我们通过在下一节中创建一个多对多的关联来定义它。\n\n## 多对多关联 (BelongsToMany)\n\n多对多关联用于将源与多个目标相连接。 此外，目标也可以连接到多个源。\n\n```js\nProject.belongsToMany(User, { through: \'UserProject\' })\nUser.belongsToMany(Project, { through: \'UserProject\' })\n```\n\n这将创建一个名为 `UserProject` 的新模型，具有等效的外键 `projectId` 和 `userId`。 属性是否为 `camelcase` 取决于由表（在这种情况下为 `User` 和 `Project`）连接的两个模型。\n\n```sql\nCREATE TABLE IF NOT EXISTS `UserProject` (\n    `createdAt` DATETIME NOT NULL,\n    `updatedAt` DATETIME NOT NULL,\n    `projectId` INTEGER,\n    `userId` INTEGER,\n    PRIMARY KEY (`projectId`, `userId`),\n    FOREIGN KEY (`projectId`) REFERENCES `projects` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n    FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE\n) ENGINE = InnoDB;\n```\n\n定义 `through` 为 `required`。 `Sequelize` 以前会尝试自动生成名称，但并不总是导致最合乎逻辑的设置。\n\n这将添加方法 `getUsers`,`setUsers`, `addUser`, `addUsers` 到 `Project`, 还有 `getProjects`, `setProjects`, `addProject`, 和 `addProjects` 到 `User`.\n\n有时，您可能需要在关联中使用它们时重命名模型。 让我们通过使用别名（`as`）选项将 `users` 定义为 `workers` 而 `projects` 定义为 `t asks`。 我们还将手动定义要使用的外键：\n\n```js\nUser.belongsToMany(Project, { as: \'Tasks\', through: \'worker_tasks\', foreignKey: \'userId\' })\nProject.belongsToMany(User, { as: \'Workers\', through: \'worker_tasks\', foreignKey: \'projectId\' })\n```\n\n- `foreignKey` 将允许你在 `through` 关系中设置 `source model` 键。\n- `otherKey` 将允许你在 `through` 关系中设置 `target model` 键。\n\n```js\nUser.belongsToMany(Project, { as: \'Tasks\', through: \'worker_tasks\', foreignKey: \'userId\', otherKey: \'projectId\' })\n```\n\n当然你也可以使用 `belongsToMany` 定义自我引用：\n\n```js\nPerson.belongsToMany(Person, { as: \'Children\', through: \'PersonChildren\' })\n// 这将创建存储对象的 ID 的表 PersonChildren。\n```\n\n如果您想要连接表中的其他属性，则可以在定义关联之前为连接表定义一个模型，然后再说明它应该使用该模型进行连接，而不是创建一个新的关联：\n\n```js\nconst User = sequelize.define(\'user\', {})\nconst Project = sequelize.define(\'project\', {})\nconst UserProjects = sequelize.define(\'userProjects\', {\n  status: DataTypes.STRING\n})\n\nUser.belongsToMany(Project, { through: UserProjects })\nProject.belongsToMany(User, { through: UserProjects })\n```\n\n要向 `user` 添加一个新 `project` 并设置其状态，您可以将额外的 `options.through` 传递给 `setter`，其中包含连接表的属性\n\n```js\nuser.addProject(project, { through: { status: \'started\' } })\n```\n\n默认情况下，上面的代码会将 `projectId` 和 `userId` 添加到 `UserProjects` 表中， 删除任何先前定义的主键属性 - 表将由两个表的键的组合唯一标识，并且没有其他主键列。 要在 `UserProjects` 模型上强添加一个主键，您可以手动添加它。\n\n```js\nconst UserProjects = sequelize.define(\'userProjects\', {\n  id: {\n    type: Sequelize.INTEGER,\n    primaryKey: true,\n    autoIncrement: true\n  },\n  status: DataTypes.STRING\n})\n```\n\n使用多对多你可以基于 `through` 关系查询并选择特定属性。 例如通过 `through` 使用 `findAll`\n\n```js\nUser.findAll({\n  include: [\n    {\n      model: Project,\n      through: {\n        attributes: [\'createdAt\', \'startedAt\', \'finishedAt\'],\n        where: { completed: true }\n      }\n    }\n  ]\n})\n```\n\n## 参考\n\n- [模型（表）之间的关系/关联](https://itbilu.com/nodejs/npm/41qaV3czb.html#associations-naming)\n- [Associations - 关联](https://github.com/demopark/sequelize-docs-Zh-CN/blob/master/associations.md)', '2019-02-11 12:41:53', '2019-02-11 12:41:53');
INSERT INTO `article` VALUES (27, 'Sequelize - 单表 CURD', '## Create\n\n### create - 创建保存新实例\n\n> create(values, [options]) -> Promise.<Instance>\n\n构建一个新的模型实例，并进行保存。与 `build()`方法不同的是，此方法除创建新实例外，还会将其保存到对应数据库表中。\n\n```js\n// 直接操作db\nconst user = await UserModel.create({\n  name: \'guodada\',\n  age: 23,\n  sex: 1,\n  score: 99\n})\n```\n<!-- more -->\n\n| 名称                         | 类型          | 说明                                        |\n| ---------------------------- | ------------- | ------------------------------------------- |\n| values                       | `Object`      | 无                                          |\n| [options]                    | `Object`      | 无                                          |\n| [options.raw=false]          | `Boolean`     | 设置为true时，值会忽略字段和虚拟设置器      |\n| [options.isNewRecord=true]   | `Boolean`     | 无                                          |\n| [options.fields]             | `Array`       | 如果设置后，只有列表中区别的列才会进行保存  |\n| [options.include]            | `Array`       | 用于构建prefetched/included模型，参见 set   |\n| [options.onDuplicate]        | `String`      | 无                                          |\n| [options.transaction]        | `Transaction` | 在事务中执行查询                            |\n| [options.logging=false]      | `Function`    | 一个用于打印查询时所执行sql的函数           |\n| [options.searchPath=DEFAULT] | `String`      | 指定schema的 search_path (仅 Postgres)      |\n| [options.benchmark=false]    | `Boolean`     | 当打印SQL日志时同时输出查询执行时间（毫秒） |\n\n### build - 创建新实例\n\n> build(values, [options]) -> Instance\n\n```js\n// build后对象只存在于内存中，调用save后才操作db\nconst user = UserModel.build({\n  name: \'guodada\',\n  age: 23,\n  sex: 1,\n  score: 99\n})\nconst result = await user.save()\nconsole.log(user.get({ plain: true })) \n```\n\n| 名称                       | 类型    | 说明                                        |\n| -------------------------- | ------- | ------------------------------------------- |\n| values                     | Object  | 无                                          |\n| [options]                  | Object  | 无                                          |\n| [options.raw=false]        | Boolean | 设置为true时，值会忽略字段和虚拟设置器      |\n| [options.isNewRecord=true] | Boolean | 无                                          |\n| [options.include]          | Array   | 用于构建`prefetched/included`模型，参见 set |\n\n## Update\n\n### update - 更新记录\n\n> update(values, options) -> Promise.<Array.<affectedCount, affectedRows>>\n\n更新所匹配的多个实例。promise 回调中会返回一个包含一个或两个元素的数组，第一个元素始终表示受影响的行数，\n第二个元素表示实际影响的行（仅 Postgreoptions.returning 为 true 时受支持）\n\n\n```js\nawait UserModel.update({ name: \'guoxiaoxiao\', age: 18 }, { where: { id: 1 } })\n```\n\n| 名称                            | 类型          | 说明                                     |\n| ------------------------------- | ------------- | ---------------------------------------- |\n| values                          | `Object`      | 无                                       |\n| options                         | `Object`      | 无                                       |\n| options.where                   | `Object`      | 筛选条件                                 |\n| [options.fields]                | `Array`       | 要更新字段，默认为全部                   |\n| [options.validate=true]         | `Boolean`     | 更新每条记录前进行验证                   |\n| [options.hooks=true]            | `Boolean`     | 在执行更新前/后创建钩子                  |\n| [options.individualHooks=false] | `Boolean`     | 在执行更新前/后为每个实例创建钩子        |\n| [options.sideEffects=true]      | `Boolean`     | 是否更新任何虚拟设置                     |\n| [options.returning=false]       | `Boolean`     | 返回受影响的行 (仅适用于 postgres)       |\n| [options.limit]                 | `Number`      | 要更新的行数 (仅适用于 mysql 和 mariadb) |\n| [options.transaction]           | `Transaction` | 在事务中执行查询                         |\n| [options.silent=false]          | `Boolean`     | 如果为true，updatedAt字段将不会更新      |\n\n\n## Read\n\n详见 [Sequelize - 使用 model 查询数据](https://gershonv.github.io/2019/01/03/sequelize-query/)\n\n## Delete\n\n### destroy - 删除记录\n\n> destroy(options) -> Promise.<Integer>\n\n删除多个实例，或设置 `deletedAt` 的时间戳为当前时间（当启用 `paranoid` 时）\n\n执行成功后返回被删除的行数\n\n```js\nconst deleteRowsCount = await UserModel.destroy({\n  where: { id: 2 }\n})\nconsole.log(deleteRowsCount) // 执行成功后返回被删除的行数\n```\n\n| 名称                            | 类型          | 说明                                                                    |\n| ------------------------------- | ------------- | ----------------------------------------------------------------------- |\n| options                         | Object        |\n| [options.where]                 | `Object`      | 筛选条件                                                                |\n| [options.hooks=true]            | `Boolean`     | 在执行前/后创建钩子                                                     |\n| [options.individualHooks=false] | `Boolean`     | 在执行前/后为每个实例创建钩子                                           |\n| [options.limit]                 | `Number`      | 要删除的行数                                                            |\n| [options.force=false]           | `Boolean`     | 删除而不是设置 deletedAt 为当前时间戳 (仅启用 paranoid 时适用)          |\n| [options.truncate=false]        | `Boolean`     | 设置为true时，会使用TRUNCATE代替DELETE FROM，这时会忽略where和limit选项 |\n| [options.cascade=false]         | `Boolean`     | 仅适用于连接查询时的TRUNCATE操作，截断所有外键匹配的表                  |\n| [options.transaction]           | `Transaction` | 在事务中执行查询                                                        |\n\n## findOrCreate - 查找或创建\n\n> findOrCreate(options) -> Promise.<Instance, created>\n\n查找一行记录，如果不存在则创建实例并保存到数据库中\n\n在这个方法中，如果options对象中没有传入事务，那么会在内部自动创建一个新的事务，以防止在创建完成之前有新匹配查询进入。\n\n```js\n// findOrCreate 返回一个包含已找到或创建的对象的数组，找到或创建的对象和一个布尔值\nUserModel.findOrCreate({\n  defaults: { name: \'guoxiaoxiao\' },\n  where: { name: \'guoxiaoxiao\' }\n}).spread((user, created) => {\n  console.log(user.name, created)\n})\n\n// 在上面的例子中，\".spread\" 将数组分成2部分，并将它们作为参数传递给回调函数，在这种情况下将它们视为 \"user\" 和 \"created\" 。\n// 所以“user”将是返回数组的索引0的对象，并且 \"created\" 将等于 \"true\"。）\n\n```\n| 名称                  | 类型          | 说明                   |\n| --------------------- | ------------- | ---------------------- |\n| options               | `Object`      | 无                     |\n| options.where         | `Object`      | 查询属性               |\n| [options.defaults]    | `Object`      | 用于创建新实例的默认值 |\n| [options.transaction] | `Transaction` | 在事务中执行查询       |\n\n## findCreateFind - 查找或创建\n\n> findCreateFind(options) -> Promise.<Instance, created>\n\n效率更高的 `findOrCreate`，不会在事务中执行。首先会尝试进行查询，如果为空则尝试创建，如果是唯一约束则尝试再次查找。\n\n| 名称                  | 类型          | 说明                   |\n| --------------------- | ------------- | ---------------------- |\n| options               | `Object`      | 无                     |\n| options.where         | `Object`      | 查询属性               |\n| [options.defaults]    | `Object`      | 用于创建新实例的默认值 |\n| [options.transaction] | `Transaction` | 在事务中执行查询       |\n\nps: `findOrInitialize`  - 查找或初始化: 查找一行记录，如果不存在则创建（不保存）实例\n\n## insertOrUpdate - 更新或创建\n\n> upsert(values, [options]) -> Promise.<created>\n\n创建或更新一行。如果匹配到主键或唯一约束键时会进行更新。\n\n```js\nconst isCreate = await TaskModel.insertOrUpdate({ title: \'11\', content: \'adfadf\' })\n// isCreate true 创建成功 false 修改成功~\n```\n\n| 名称                                          | 类型          | 说明                      |\n| --------------------------------------------- | ------------- | ------------------------- |\n| values                                        | `Object`      | 无                        |\n| [options]                                     | `Object`      | 无                        |\n| [options.validate=true]                       | `Boolean`     | 插入前进行验证            |\n| [options.fields=Object.keys(this.attributes)] | `Array`       | 要插入/更新字段。默认全部 |\n| [options.transaction]                         | `Transaction` | 在事务中执行查询          |\n\n## bulkCreate - 创建多条记录\n\n> bulkCreate(records, [options]) -> Promise.<Array.<Instance>>\n\n批量创建并保存多个实例。\n\n处理成功后，会在回调函数中返回一个包含多个实例的数组。\n\n```js\n const users = await UserModel.bulkCreate([\n  { name: \'guo\', age: 22, sex: 1 },\n  { name: \'guo2\', age: 12, sex: 0 },\n  { name: \'guo3\', age: 32, sex: 1 }\n])\n```\n\n| 名称                             | 类型          | 说明                                                      |\n| -------------------------------- | ------------- | --------------------------------------------------------- |\n| records                          | `Array`       | 要创建实例的对象（键/值 对）列表                          |\n| [options]                        | `Object`      | 无                                                        |\n| [options.fields]                 | `Array`       | 要插入的字段。默认全部                                    |\n| [options.validate=true]          | `Boolean`     | 插入每条记录前进行验证                                    |\n| [options.hooks=true]             | `Boolean`     | 在执行前/后创建钩子                                       |\n| [options.individualHooks=false]  | `Boolean`     | 在执行前/后为每个实例创建钩子                             |\n| [options.ignoreDuplicates=false] | `Boolean`     | 忽略重复主键（Postgres不支持）                            |\n| [options.updateOnDuplicate]      | `Array`       | 如果行键已存在是否更新（mysql & mariadb支持）. 默认为更新 |\n| [options.transaction]            | `Transaction` | 在事务中执行查询                                          |', '2019-02-11 12:42:16', '2019-02-11 12:42:16');
INSERT INTO `article` VALUES (28, 'Sequelize - 多表 CURD', '## 一对一\n\n```js\nconst UserModel = sequelize.define(\'user\', {\n  uuid: {\n    type: Sequelize.INTEGER,\n    allowNull: false,\n    unique: true\n  }\n})\n\nconst AccountModel = sequelize.define(\'account\', {\n  email: {\n    type: Sequelize.CHAR(60),\n    allowNull: false\n  }\n})\n\n//  User的实例对象将拥有 getAccount、setAccount、createAccount 方法\nUserModel.hasOne(AccountModel)\n\n// Account的实例对象将拥有getUser、setUser、addUser 方法\nAccountModel.belongsTo(UserModel, {\n  foreignKey: \'userId\',\n  targetKey: \'uuid\'\n})\n```\n\n<!-- more -->\n\n### 增 - createAccount\n\n```js\n// 增\nconst user = await UserModel.create({ uuid: 666 })\nconst account = await user.createAccount({ email: \'12306@qq.com\' }) // 增\n\nconsole.log(account.get({ plain: true }))\n```\n\n使用对应的的 `userId` 作为外键在 `accounts` 表里插入一条数据。\n\n```js\nmysql> select * from users;\n+----+------+---------------------+---------------------+\n| id | uuid | createdAt           | updatedAt           |\n+----+------+---------------------+---------------------+\n|  1 |  666 | 2019-01-11 05:23:05 | 2019-01-11 05:23:05 |\n+----+------+---------------------+---------------------+\n1 row in set (0.00 sec)\n\nmysql> select * from accounts;\n+----+--------------+---------------------+---------------------+--------+\n| id | email        | createdAt           | updatedAt           | userId |\n+----+--------------+---------------------+---------------------+--------+\n|  1 | 12306@qq.com | 2019-01-11 05:23:05 | 2019-01-11 08:05:38 |      1 |\n+----+--------------+---------------------+---------------------+--------+\n1 row in set (0.00 sec)\n```\n\n### 改 - setAccount\n\n```js\nconst user = await UserModel.findByPk(1)\nconst antherAccount = await AccountModel.create({ email: \'aaa\' })\nconst account = await user.setAccount(antherAccount)\n```\n\n1. 插入一条 `account` 数据，此时外键 `userId` 是空的，还没有关联 `user`\n2. 找出当前 `user` 所关联的 `account` 并将其 `userId` 置为 `NULL`（为了保证一对一关系）\n3. 设置新的 `acount` 的外键 `userId` 为 `user` 的属性 `id`，生成关系\n\n```js\nmysql> select * from accounts;\n+----+--------------+---------------------+---------------------+--------+\n| id | email        | createdAt           | updatedAt           | userId |\n+----+--------------+---------------------+---------------------+--------+\n|  1 | 12306@qq.com | 2019-01-11 05:23:05 | 2019-01-11 08:11:59 |   NULL |\n|  2 | aaa          | 2019-01-11 08:11:59 | 2019-01-11 08:11:59 |      1 |\n+----+--------------+---------------------+---------------------+--------+\n2 rows in set (0.00 sec)\n```\n\n### 软删 - setAccount(null)\n\n```js\nconst user = await UserModel.findByPk(1)\nconst account = await user.setAccount(null)\n```\n\n这里的删除实际上只是“切断”关系，并不会真正的物理删除记录。\nSQL 执行逻辑是：\n\n1. 找出 `user` 所关联的 `account` 数据\n2. 将其外键 `userId` 设置为 `NULL`，完成关系的“切断”\n\n### 查 - getAccount\n\n```js\nconst user = await UserModel.findByPk(1)\nconst account = await user.getAccount()\nconsole.log(account.get({ plain: true }))\n```\n\nor\n\n```js\nconst user = await UserModel.findByPk(1, {\n  include: [AccountModel]\n})\nconsole.log(user.get({ plain: true }))\n// { id: 1,\n//   uuid: 666,\n//   createdAt: 2019-01-11T05:23:05.000Z,\n//   updatedAt: 2019-01-11T05:23:05.000Z,\n//   account:\n//    { id: 2,\n//      email: \'aaa\',\n//      createdAt: 2019-01-11T08:11:59.000Z,\n//      updatedAt: 2019-01-11T08:11:59.000Z,\n//      userId: 1 } }\n```\n\n```js\nmysql> SELECT `user`.`id`, `user`.`uuid`, `user`.`createdAt`, `user`.`updatedAt`,\n`account`.`id` AS `account.id`, `account`.`email` AS `account.email`, `account`.`createdAt` AS `account.createdAt`,\n`account`.`updatedAt` AS `account.updatedAt`, `account`.`userId` AS `account.userId` FROM `users` AS `user`\nLEFT OUTER JOIN `accounts` AS `account` ON `user`.`id` = `account`.`userId` WHERE `user`.`id` = 1;\n\n+----+------+---------------------+---------------------+------------+---------------+---------------------+---------------------+----------------+\n| id | uuid | createdAt           | updatedAt           | account.id | account.email | account.createdAt   | account.updatedAt   | account.userId |\n+----+------+---------------------+---------------------+------------+---------------+---------------------+---------------------+----------------+\n|  1 |  666 | 2019-01-11 05:23:05 | 2019-01-11 05:23:05 |          9 | aaa           | 2019-01-11 08:11:59 | 2019-01-11 08:11:59 |              1 |\n+----+------+---------------------+---------------------+------------+---------------+---------------------+---------------------+----------------+\n1 row in set (0.00 sec)\n```\n\n可以看到，我们对 2 个表进行了一个外联接，从而在取 `user` 的同时也获取到了 `account`。\n\n## 一对多\n\n```js\nconst UserModel = sequelize.define(\n  \'user\',\n  {\n    uuid: {\n      type: Sequelize.INTEGER,\n      allowNull: false,\n      unique: true\n    }\n  },\n  { timestamps: false }\n)\n\nconst NoteModel = sequelize.define(\'note\', {\n  title: {\n    type: Sequelize.CHAR(64),\n    allowNull: false\n  }\n})\n\n// User的实例对象将拥有getNotes、setNotes、addNote、createNote、removeNote、hasNote方法\nUserModel.hasMany(NoteModel)\n\n// Note的实例对象将拥有getUser、setUser、createUser方法\nNoteModel.belongsTo(UserModel)\n```\n\n生成的 sql 语句：\n\n```sql\nCREATE TABLE IF NOT EXISTS `users` (\n    `id` INTEGER NOT NULL auto_increment ,\n    `uuid` INTEGER NOT NULL UNIQUE, PRIMARY KEY (`id`)\n  ) ENGINE=InnoDB;\n\nCREATE TABLE IF NOT EXISTS `notes` (\n    `id` INTEGER NOT NULL auto_increment,\n    `title` CHAR(64) NOT NULL,\n    `createdAt` DATETIME NOT NULL,\n    `updatedAt` DATETIME NOT NULL,\n    `userId` INTEGER,\n    PRIMARY KEY (`id`),\n    FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE\n    SET\n        NULL ONUPDATE CASCADE\n) ENGINE = InnoDB;\n```\n\n可以看到这种关系中，外键 `userId` 加在了多的一端（`notes` 表）。同时相关的模型也自动获得了一些方法。\n\n### 增\n\n#### createNote\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note1 = await user.createNote({ title: \'aa\' }) // 增\n\nconsole.log(note1.get({ plain: true }))\n```\n\nSQL 执行逻辑：\n\n> 使用 `user` 的主键 `id` 值作为外键直接在 `notes` 表里插入一条数据。\n\n```js\nINSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`,`userId`)\nVALUES (DEFAULT,\'aa\',\'2019-01-12 05:32:50\',\'2019-01-12 05:32:50\',1);\n```\n\n#### addNote\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note = await NoteModel.create({ title: \'bb\' })\nawait user.addNote(note)\n```\n\n`SQL`:\n\n```sql\nINSERT INTO `users` (`id`,`uuid`) VALUES (DEFAULT,1234);\nINSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`) VALUES (DEFAULT,\'bb\',\'2019-01-12 05:40:34\',\'2019-01-12 05:40:34\');\nUPDATE `notes` SET `userId`=1,`updatedAt`=\'2019-01-12 05:40:34\' WHERE `id` IN (1)\n```\n\n1. 插入一条 `note` 数据，此时该条数据的外键 `userId` 为空\n2. 使用 `user` 的属性 `id` 值再更新该条 `note` 数据，设置好外键，完成关系建立\n\n#### addNotes\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note1 = await NoteModel.create({ title: \'aa\' })\nconst note2 = await NoteModel.create({ title: \'bb\' })\n\nawait user.addNotes([note1, note2])\n```\n\n### 改 - setNotes\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note1 = await user.createNote({ title: \'aa\' })\nconst note2 = await user.createNote({ title: \'bb\' })\n\nconst note3 = await NoteModel.create({ title: \'cc\' })\nconst note4 = await NoteModel.create({ title: \'dd\' })\n\nawait user.setNotes([note3, note4])\n```\n\n1. 根据 `user` 的属性 id 查询所有相关的 `note` 数据\n2. 将 `note1`、`note2` 的外键 `userId` 置为 `NULL`，切断关系\n3. 将 `note3`、`note4` 的外键 `userId` 置为 `user` 的属性 `id`，完成关系建立\n\n```js\nmysql> select * from notes;\n+----+-------+---------------------+---------------------+--------+\n| id | title | createdAt           | updatedAt           | userId |\n+----+-------+---------------------+---------------------+--------+\n|  1 | aa    | 2019-01-12 05:53:11 | 2019-01-12 05:53:11 |   NULL |\n|  2 | bb    | 2019-01-12 05:53:11 | 2019-01-12 05:53:11 |   NULL |\n|  3 | cc    | 2019-01-12 05:53:11 | 2019-01-12 05:53:11 |      1 |\n|  4 | dd    | 2019-01-12 05:53:11 | 2019-01-12 05:53:11 |      1 |\n+----+-------+---------------------+---------------------+--------+\n4 rows in set (0.00 sec)\n```\n\n### 软删\n\n#### removeNote\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note1 = await user.createNote({ title: \'aa\' })\nconst note2 = await user.createNote({ title: \'bb\' })\n\nawait user.removeNote(note1)\n```\n\n```js\nmysql> select * from notes;\n+----+-------+---------------------+---------------------+--------+\n| id | title | createdAt           | updatedAt           | userId |\n+----+-------+---------------------+---------------------+--------+\n|  1 | aa    | 2019-01-12 06:05:40 | 2019-01-12 06:05:40 |   NULL |\n|  2 | bb    | 2019-01-12 06:05:40 | 2019-01-12 06:05:40 |      1 |\n+----+-------+---------------------+---------------------+--------+\n2 rows in set (0.00 sec)\n```\n\n#### setNotes([])\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note1 = await user.createNote({ title: \'aa\' })\nconst note2 = await user.createNote({ title: \'bb\' })\nawait user.setNotes([])\n```\n\n### 查\n\n#### getNotes\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note1 = await user.createNote({ title: \'aa\' })\nconst note2 = await user.createNote({ title: \'bb\' })\n\nconst notes = await user.getNotes({\n  where: {\n    title: { $like: \'%a%\' }\n  }\n})\nnotes.map(note => console.log(note.title))\n```\n\n#### findAll\n\n> 场景 1： 查询所有满足条件的 `note`，同时获取 `note` 属于哪个 `user`。\n\n```js\nconst notes = await NoteModel.findAll({\n  include: UserModel, // or [UserModel, ...]\n  where: {\n    title: { $like: \'%a%\' }\n  }\n})\n\nnotes.map(note => {\n  console.log(note.get({ plain: true }))\n})\n\n// result\n\n// { id: 1,\n//   title: \'aa\',\n//   createdAt: 2019-01-12T06:12:33.000Z,\n//   updatedAt: 2019-01-12T06:12:33.000Z,\n//   userId: 1,\n//   user: { id: 1, uuid: 1234 } }\n```\n\n> 场景 2 查询所有满足条件的 `note`，同时获取 `note` 属于哪个`user`。\n\n```js\nconst user = await UserModel.create({ uuid: 1234 })\nconst note1 = await user.createNote({ title: \'aa\' })\nconst note2 = await user.createNote({ title: \'bb\' })\n\nconst users = await UserModel.findAll({\n  include: [NoteModel], // or [UserModel, ...]\n  where: { uuid: 1234 }\n})\nusers.map(note => {\n  console.log(note.get({ plain: true }))\n})\n\n// result\n// { id: 1,\n//   uuid: 1234,\n//   notes:\n//    [ { id: 1,\n//        title: \'aa\',\n//        createdAt: 2019-01-12T06:17:39.000Z,\n//        updatedAt: 2019-01-12T06:17:39.000Z,\n//        userId: 1 },\n//      { id: 2,\n//        title: \'bb\',\n//        createdAt: 2019-01-12T06:17:39.000Z,\n//        updatedAt: 2019-01-12T06:17:39.000Z,\n//        userId: 1 } ] }\n```\n\n```js\n// 查询创建时间在今天之前的所有user，同时获取他们note的标题中含有关键字css的所有note\nconst users = await UserModel.findAll({\n  include: [\n    {\n      model: NoteModel,\n      where: {\n        title: { $like: \'%aa%\' }\n      }\n    }\n  ],\n  where: { uuid: 1234 }\n})\n```\n\n## 多对多\n\n```js\nconst NoteModel = sequelize.define(\'note\', {\n  title: {\n    type: Sequelize.CHAR(64),\n    allowNull: false\n  }\n})\n\nconst TagModel = sequelize.define(\'tag\', {\n  name: Sequelize.CHAR(64)\n})\n\nconst TaggingModel = sequelize.define(\'tagging\', {\n  type: Sequelize.INTEGER\n})\n\n// Note的实例拥有getTags、setTags、addTag、addTags、createTag、removeTag、hasTag方法\nNoteModel.belongsToMany(TagModel, {\n  through: TaggingModel\n})\n\n// Tag的实例拥有getNotes、setNotes、addNote、addNotes、createNote、removeNote、hasNote方法\nTagModel.belongsToMany(NoteModel, {\n  through: TaggingModel\n})\n```\n\n`tagging` 表连接两个表：\n\n```js\nmysql> show columns from taggings;\n+-----------+----------+------+-----+---------+-------+\n| Field     | Type     | Null | Key | Default | Extra |\n+-----------+----------+------+-----+---------+-------+\n| type      | int(11)  | YES  |     | NULL    |       |\n| createdAt | datetime | NO   |     | NULL    |       |\n| updatedAt | datetime | NO   |     | NULL    |       |\n| noteId    | int(11)  | NO   | PRI | NULL    |       |\n| tagId     | int(11)  | NO   | PRI | NULL    |       |\n+-----------+----------+------+-----+---------+-------+\n5 rows in set (0.00 sec)\n```\n\n### 增\n\n#### createTag\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nawait note.createTag({ name: \'react.js\' }, { through: { type: 0 } })\n```\n\nSQL:\n\n```sql\nINSERT INTO `notes`\n  (`id`,`title`,`createdAt`,`updatedAt`) VALUES (DEFAULT,\'aa\',\'2019-01-12 06:38:09\',\'2019-01-12 06:38:09\');\n\nINSERT INTO `tags`\n  (`id`,`name`,`createdAt`,`updatedAt`) VALUES (DEFAULT,\'react.js\',\'2019-01-12 06:38:09\',\'2019-01-12 06:38:09\');\n\nINSERT INTO `taggings`\n  (`type`,`createdAt`,`updatedAt`,`noteId`,`tagId`) VALUES (0,\'2019-01-12 06:51:10\',\'2019-01-12 06:51:10\',1,1);\n```\n\n1. 在 `notes` 表插入记录\n2. 在 `tags` 表中插入记录\n3. 使用对应的值设置外键 `tagId` 和 `noteId` 以及关系模型本身需要的属性（`type: 0`）在关系表 tagging 中插入记录\n\n```js\nmysql> select * from notes;\n+----+-------+---------------------+---------------------+\n| id | title | createdAt           | updatedAt           |\n+----+-------+---------------------+---------------------+\n|  1 | aa    | 2019-01-12 06:51:10 | 2019-01-12 06:51:10 |\n+----+-------+---------------------+---------------------+\n1 row in set (0.00 sec)\n\nmysql> select * from taggings;\n+------+---------------------+---------------------+--------+-------+\n| type | createdAt           | updatedAt           | noteId | tagId |\n+------+---------------------+---------------------+--------+-------+\n|    0 | 2019-01-12 06:51:10 | 2019-01-12 06:51:10 |      1 |     1 |\n+------+---------------------+---------------------+--------+-------+\n1 row in set (0.00 sec)\n\nmysql> select * from tags;\n+----+----------+---------------------+---------------------+\n| id | name     | createdAt           | updatedAt           |\n+----+----------+---------------------+---------------------+\n|  1 | react.js | 2019-01-12 06:55:00 | 2019-01-12 06:55:00 |\n+----+----------+---------------------+---------------------+\n1 row in set (0.00 sec)\n```\n\n#### addTag\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nconst tag = await TagModel.create({ name: \'react.js\' })\nawait note.addTag(tag, { through: { type: 1 } })\n```\n\n#### addTags\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nconst tag1 = await TagModel.create({ name: \'react.js\' })\nconst tag2 = await TagModel.create({ name: \'vue.js\' })\nawait note.addTags([tag1, tag2], { through: { type: 1 } })\n```\n\n### 改 - setTags\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nconst tag1 = await TagModel.create({ name: \'react.js\' })\nconst tag2 = await TagModel.create({ name: \'vue.js\' })\n\nawait note.addTags([tag1, tag2], { through: { type: 1 } })\n\nconst tag3 = await TagModel.create({ name: \'angular.js\' })\nconst tag4 = await TagModel.create({ name: \'ant-desgin\' })\n\nawait note.setTags([tag3, tag4], { through: { type: 2 } })\n```\n\n```js\nmysql> select * from taggings;\n+------+---------------------+---------------------+--------+-------+\n| type | createdAt           | updatedAt           | noteId | tagId |\n+------+---------------------+---------------------+--------+-------+\n|    2 | 2019-01-12 07:02:52 | 2019-01-12 07:02:52 |      1 |     3 |\n|    2 | 2019-01-12 07:02:52 | 2019-01-12 07:02:52 |      1 |     4 |\n+------+---------------------+---------------------+--------+-------+\n2 rows in set (0.00 sec)\n\nmysql> select * from tags;\n+----+------------+---------------------+---------------------+\n| id | name       | createdAt           | updatedAt           |\n+----+------------+---------------------+---------------------+\n|  1 | react.js   | 2019-01-12 07:02:51 | 2019-01-12 07:02:51 |\n|  2 | vue.js     | 2019-01-12 07:02:51 | 2019-01-12 07:02:51 |\n|  3 | angular.js | 2019-01-12 07:02:52 | 2019-01-12 07:02:52 |\n|  4 | ant-desgin | 2019-01-12 07:02:52 | 2019-01-12 07:02:52 |\n+----+------------+---------------------+---------------------+\n4 rows in set (0.00 sec)\n```\n\n### 删\n\n#### removeTag\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nconst tag1 = await TagModel.create({ name: \'react.js\' })\nconst tag2 = await TagModel.create({ name: \'vue.js\' })\n\nawait note.addTags([tag1, tag2], { through: { type: 1 } })\n\nawait note.removeTag(tag2)\n\n// DELETE FROM `taggings` WHERE `noteId` = 1 AND `tagId` IN (2)\n```\n\n--> 软删:\n\n```js\nmysql> select * from tags;\n+----+----------+---------------------+---------------------+\n| id | name     | createdAt           | updatedAt           |\n+----+----------+---------------------+---------------------+\n|  1 | react.js | 2019-01-12 07:06:18 | 2019-01-12 07:06:18 |\n|  2 | vue.js   | 2019-01-12 07:06:18 | 2019-01-12 07:06:18 |\n+----+----------+---------------------+---------------------+\n2 rows in set (0.00 sec)\n\nmysql> select * from taggings;\n+------+---------------------+---------------------+--------+-------+\n| type | createdAt           | updatedAt           | noteId | tagId |\n+------+---------------------+---------------------+--------+-------+\n|    1 | 2019-01-12 07:06:18 | 2019-01-12 07:06:18 |      1 |     1 |\n+------+---------------------+---------------------+--------+-------+\n1 row in set (0.00 sec)\n```\n\n#### setTags([])\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nconst tag1 = await TagModel.create({ name: \'react.js\' })\nconst tag2 = await TagModel.create({ name: \'vue.js\' })\n\nawait note.addTags([tag1, tag2], { through: { type: 1 } })\n\nawait note.setTags([])\n```\n\n### 查\n\n#### getTags\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nconst tag1 = await TagModel.create({ name: \'react.js\' })\nconst tag2 = await TagModel.create({ name: \'vue.js\' })\n\nawait note.addTags([tag1, tag2], { through: { type: 1 } })\n\nconst notes = await note.getTags({\n  // 这里可以对tags进行where\n})\nnotes.map(d => {\n  console.log(d.get({ plain: true }))\n})\n\n// { id: 1,\n//   name: \'react.js\',\n//   createdAt: 2019-01-12T07:11:26.000Z,\n//   updatedAt: 2019-01-12T07:11:26.000Z,\n//   tagging:\n//    { type: 1,\n//      createdAt: 2019-01-12T07:11:26.000Z,\n//      updatedAt: 2019-01-12T07:11:26.000Z,\n//      noteId: 1,\n//      tagId: 1 } }\n// { id: 2,\n//   name: \'vue.js\',\n//   createdAt: 2019-01-12T07:11:26.000Z,\n//   updatedAt: 2019-01-12T07:11:26.000Z,\n//   tagging:\n//    { type: 1,\n//      createdAt: 2019-01-12T07:11:26.000Z,\n//      updatedAt: 2019-01-12T07:11:26.000Z,\n//      noteId: 1,\n//      tagId: 2 } }\n```\n\n可以看到这种查询，就是执行一个 `inner join`。\n\n#### findAll\n\n```js\nconst note = await NoteModel.create({ title: \'aa\' })\nconst tag1 = await TagModel.create({ name: \'react.js\' })\nconst tag2 = await TagModel.create({ name: \'vue.js\' })\n\nawait note.addTags([tag1, tag2], { through: { type: 1 } })\n\nconst notes = await NoteModel.findAll({\n  include: TagModel\n})\n\nnotes.map(d => {\n  console.log(d.get({ plain: true }))\n})\n\n// { id: 1,\n//   title: \'aa\',\n//   createdAt: 2019-01-12T07:14:43.000Z,\n//   updatedAt: 2019-01-12T07:14:43.000Z,\n//   tags:\n//    [ { id: 1,\n//        name: \'react.js\',\n//        createdAt: 2019-01-12T07:14:43.000Z,\n//        updatedAt: 2019-01-12T07:14:43.000Z,\n//        tagging: [Object] },\n//      { id: 2,\n//        name: \'vue.js\',\n//        createdAt: 2019-01-12T07:14:43.000Z,\n//        updatedAt: 2019-01-12T07:14:43.000Z,\n//        tagging: [Object] } ] }\n```', '2019-02-11 12:42:39', '2019-02-11 12:42:39');
INSERT INTO `article` VALUES (29, 'Vue render 函数解析', '## Vue的一些基本概念\n\n使用 Vue 编写可复用组件，那么要对 render 函数有所了解。今天我们学习的目的是了解和学习Vue的`render`函数。如果想要更好的学习Vue的`render`函数相关的知识，我们有必要重温一下Vue中的一些基本概念。那么先上一张图，这张图从宏观上展现了Vue整体流程：\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-1.jpg)\n从上图中，不难发现一个Vue的应用程序是如何运行起来的，模板通过编译生成AST，再由AST生成Vue的render函数（渲染函数），渲染函数结合数据生成`Virtual DOM`树，`Diff`和`Patch`后生成新的UI。从这张图中，可以接触到Vue的一些主要概念：\n\n- **模板**：Vue的模板基于纯HTML，基于Vue的模板语法，我们可以比较方便地声明数据和UI的关系。\n- **AST**：AST是`Abstract Syntax Tree`的简称，Vue使用HTML的`Parser`将HTML模板解析为`AST`，并且对`AST`进行一些优化的标记处理，提取最大的静态树，方便`Virtual  DOM`时直接跳过`Diff`。 \n- **渲染函数**：渲染函数是用来生成`VirtualDOM`的。Vue推荐使用模板来构建我们的应用界面，在底层实现中Vue会将模板编译成渲染函数，当然我们也可以不写模板，直接写渲染函数，以获得更好的控制\n- **Virtual DOM**：虚拟`DOM`树，Vue的`Virtual DOM Patching`算法是基于Snabbdom的实现，并在些基础上作了很多的调整和改进。\n- **Watcher**：每个Vue组件都有一个对应的`watcher`，这个`watcher`将会在组件`render`的时候收集组件所依赖的数据，并在依赖有更新的时候，触发组件重新渲染。你根本不需要写`shouldComponentUpdate`，Vue会自动优化并更新要更新的UI。\n\n<!--more-->\n\n## 渲染函数的基础\nVue推荐在绝大多数情况下使用template来创建你的HTML。然而在一些场景中，需要使用JavaScript的编程能力和创建HTML，这就是`render`函数，它比`template`更接近编译器。\n\n```html\n<h1>\n  <a name=\"hello-world\" href=\"#hello-world\">\n    Hello world!\n  </a>\n</h1>\n```\n在 HTML 层，我们决定这样定义组件接口：\n```html\n<anchored-heading :level=\"1\">Hello world!</anchored-heading>\n```\n当我们开始写一个通过 level prop 动态生成 heading 标签的组件，你可能很快想到这样实现：\n```html\n<!-- HTML -->\n<script type=\"text/x-template\" id=\"anchored-heading-template\">\n  <h1 v-if=\"level === 1\">\n    <slot></slot>\n  </h1>\n  <h2 v-else-if=\"level === 2\">\n    <slot></slot>\n  </h2>\n  <h3 v-else-if=\"level === 3\">\n    <slot></slot>\n  </h3>\n  <h4 v-else-if=\"level === 4\">\n    <slot></slot>\n  </h4>\n  <h5 v-else-if=\"level === 5\">\n    <slot></slot>\n  </h5>\n  <h6 v-else-if=\"level === 6\">\n    <slot></slot>\n  </h6>\n</script>\n\n<!-- Javascript -->\nVue.component(\'anchored-heading\', {\n  template: \'#anchored-heading-template\',\n  props: {\n    level: {\n      type: Number,\n      required: true\n    }\n  }\n})\n```\n在这种场景中使用 template 并不是最好的选择：首先代码冗长，为了在不同级别的标题中插入锚点元素，我们需要重复地使用 `<slot></slot>`。\n虽然模板在大多数组件中都非常好用，但是在这里它就不是很简洁的了。那么，我们来尝试使用 `render` 函数重写上面的例子：\n\n``` javascript\nVue.component(\'anchored-heading\', {\n  render: function (createElement) {\n    return createElement(\n      \'h\' + this.level,   // tag name 标签名称\n      this.$slots.default // 子组件中的阵列\n    )\n  },\n  props: {\n    level: {\n      type: Number,\n      required: true\n    }\n  }\n})\n```\n\n## 节点、树以及虚拟DOM\n在深入渲染函数之前，了解一些浏览器的工作原理是很重要的。以下面这段 HTML 为例：\n\n``` html\n<div>\n  <h1>My title</h1>\n  Some text content\n  <!-- TODO: Add tagline -->\n</div>\n```\n当浏览器读到这些代码时，它会建立一个[DOM节点树](https://javascript.info/dom-nodes)来保持追踪，如果你会画一张家谱树来追踪家庭成员的发展一样。\nHTML 的 DOM 节点树如下图所示：\n![enter description here](https://cn.vuejs.org/images/dom-tree.png)\n每个元素都是一个节点。每片文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样，每个节点都可以有孩子节点 (也就是说每个部分可以包含其它的一些部分)。\n高效的更新所有这些节点会是比较困难的，不过所幸你不必再手动完成这个工作了。你只需要告诉 Vue 你希望页面上的 HTML 是什么，这可以是在一个模板里：\n\n``` html\n<h1>{{ blogTitle }}</h1>\n```\n或者一个渲染函数里：\n\n``` javascript\nrender: function (createElement) {\n  return createElement(\'h1\', this.blogTitle)\n}\n```\n在这两种情况下，Vue 都会自动保持页面的更新，即便 `blogTitle` 发生了改变。\n\n### 虚拟 DOM\nVue 通过建立一个虚拟 DOM 对真实 DOM 发生的变化保持追踪。\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-2.png)\nVue的编译器在编译模板之后，会把这些模板编译成一个渲染函数。而函数被调用的时候就会渲染并且返回一个虚拟DOM的树。\n当我们有了这个虚拟的树之后，再交给一个`Patch函数`，负责把这些虚拟DOM真正施加到真实的DOM上。在这个过程中，Vue有自身的响应式系统来侦测在渲染过程中所依赖到的数据来源。在渲染过程中，侦测到数据来源之后就可以精确感知数据源的变动。到时候就可以根据需要重新进行渲染。当重新进行渲染之后，会生成一个新的树，将新的树与旧的树进行对比，就可以最终得出应施加到真实DOM上的改动。最后再通过Patch函数施加改动。\n\n简单点讲，在Vue的底层实现上，Vue将模板编译成虚拟DOM渲染函数。结合Vue自带的响应系统，在应该状态改变时，Vue能够智能地计算出重新渲染组件的最小代价并应到DOM操作上。\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2017/1711/vue-r-1.png)\nVue支持我们通过data参数传递一个JavaScript对象做为组件数据，然后Vue将遍历此对象属性，使用[Object.defineProperty方法](https://www.w3cplus.com/vue/vue-two-way-binding-object-defineproperty.html)设置描述对象，通过存取器函数可以追踪该属性的变更，Vue创建了一层Watcher层，在组件渲染的过程中把属性记录为依赖，之后当依赖项的setter被调用时，会通知Watcher重新计算，从而使它关联的组件得以更新,如下图：\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2017/1711/Object-defineProperty-14.png)\n\n有关于Vue的响应式相关的内容，可以阅读下列文章：\n\n深入理解Vue.js响应式原理\n\n - [Vue双向绑定的实现原理Object.defineproperty](https://www.w3cplus.com/vue/understanding-vue-js-reactivity-depth-object-defineproperty.html)\n - [Vue的双向绑定原理及实现](https://www.w3cplus.com/vue/vue-two-way-binding.html)\n - [Vue中的响应式](https://www.w3cplus.com/vue/vue-reactivity.html)\n - [从JavaScript属性描述器剖析Vue.js响应式视图](https://www.w3cplus.com/vue/reactive.html)\n\n对于虚拟DOM，咱们来看一个简单的实例，就是下图所示的这个，详细的阐述了`模板 → 渲染函数 → 虚拟DOM树 → 真实DOM`的一个过程\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-3.png)\n\nVue 通过建立一个虚拟 DOM 对真实 DOM 发生的变化保持追踪。请仔细看这行代码：\n\n``` javascript\nreturn createElement(\'h1\', this.blogTitle)\n```\n`createElement` 到底会返回什么呢？其实不是一个实际的 DOM 元素。它更准确的名字可能是 `createNodeDescription`，因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点，及其子节点。我们把这样的节点描述为“虚拟节点 (Virtual Node)”，也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。\n\nVue组件树建立起来的整个VNode树是唯一的。这意味着，下面的render函数是无效的：\n\n``` javascript\nrender: function (createElement) {\n  var myParagraphVNode = createElement(\'p\', \'hi\')\n  return createElement(\'div\', [\n    // 错误-重复的 VNodes\n    myParagraphVNode, myParagraphVNode\n  ])\n}\n```\n如果你真的需要重复很多次的元素/组件，你可以使用工厂函数来实现。例如，下面这个例子 `render` 函数完美有效地渲染了 20 个重复的段落：\n\n``` javascript\nrender: function (createElement) {\n  return createElement(\'div\',\n    Array.apply(null, { length: 20 }).map(function () {\n      return createElement(\'p\', \'hi\')\n    })\n  )\n}\n```\n## Vue的渲染机制\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-4.png)\n上图展示的是独立构建时的一个渲染流程图。\n\n继续使用上面用到的模板到真实DOM过程的一个图：\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-3.png)\n\n这里会涉及到Vue的另外两个概念：\n\n- 独立构建：包含模板编译器，渲染过程HTML字符串 → render函数 → VNode → 真实DOM节点\n- 运行时构建：不包含模板编译器，渲染过程render函数 → VNode → 真实DOM节点\n\n运行时构建的包，会比独立构建少一个模板编译器。在$mount函数上也不同。而$mount方法又是整个渲染过程的起始点。用一张流程图来说明：\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-5.png)\n由此图可以看到，在渲染过程中，提供了三种渲染模式，自定义render函数、template、el均可以渲染页面，也就是对应我们使用Vue时，三种写法：\n自定义render函数\n\n``` javascript\nVue.component(\'anchored-heading\', {\n  render: function(createElement) {\n    return createElement(\n      \'h\' + this.level, // tag name标签名称\n      this.$slots.default // 子组件中的阵列\n    )\n  },\n  props: {\n    level: {\n      type: Number,\n      required: true\n    }\n  }\n})\n```\ntemplate写法\n\n``` javascript\nlet app = new Vue({\n  template: `<div>{{msg}}</div>`,\n  data() {\n    return {\n      msg: \'\'\n    }\n  }\n})\n```\nel写法\n\n``` javascript\nlet app = new Vue({\n  el: \'#app\',\n  data() {\n    return {\n      msg: \'\'\n    }\n  }\n})\n```\n这三种渲染模式最终都是要得到`render`函数。只不过用户自定义的`render`函数省去了程序分析的过程，等同于处理过的`render`函数，而普通的`template`或者`el`只是字符串，需要解析成`AST`，再将`AST`转化为`render`函数。\n\n> 记住一点，无论哪种方法，都要得到`render`函数\n\n## 理解createElement 参数\n第一个参数：`{String | Object | Function}`\n第一个参数对于createElement而言是一个必须的参数，这个参数可以是字符串string、是一个对象object，也可以是一个函数function。\n\n``` javascript\n<div id=\"app\">\n	<custom-element></custom-element>\n</div>\n\nVue.component(\'custom-element\', {\n	render: function (createElement){\n		return createElement(\'div\')\n	}\n})\n\nlet app = new Vue({\n	el: \'#app\'\n})\n```\n上面的示例，给createElement传了一个String参数\'div\'，即传了一个HTML标签字符。最后会有一个div元素渲染出来。\n\n接着把上例中的String换成一个Object，比如：\n\n``` javascript\nVue.component(\'custom-element\', {\n	render: function (createElement){\n		return createElement({\n			template: `<div>Hello Vue</div>`\n		})\n	}\n})\n```\n上例传了一个{template: \'<div>Hello Vue!</div>\'}对象。此时custom-element组件渲染出来的结果如下：\n\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-7.png)\n\n除此之外，还可以传一个Function，比如：\n\n``` javascript\nVue.component(\'custom-element\', {\n	render: function (createElement){\n		var eleFun = function () {\n			return {\n				template: `<div>hello</div>`\n			}\n		}\n		return createElement(eleFun)\n	}\n})\n```\n最终得到的结果和上图是一样的。这里传了一个eleFun()函数给createElement，而这个函数返回的是一个对象。\n第二个参数: `{Object}`\ncreateElement是一个可选参数，这个参数是一个Object。来看一个小示例：\n\n``` javascript\n<div id=\"app\">\n	<custom-element></custom-element>\n</div>\n\nVue.component(\'custom-element\', {\n	render: function (createElement){\n		// 第一个参数是一个简单的HTML标签字符 “必选”\n		// 第二个参数是一个包含模板相关属性的数据对象 “可选”\n		return createElement(\'div\', {\n			\'class\': {\n				foo: true,\n				bar: false\n			},\n			style: {\n				color: \'red\',\n				fontSize: \'14px\'\n			},\n			attrs: {\n				id: \'boo\'\n			},\n			domProps: {\n				innerHTML: \'Hello Vue!\'\n			}\n		})\n	}\n})\n\nlet app = new Vue({\n	el: \'#app\'\n})\n```\n最终生成的DOM，将会带一些属性和内容的div元素，如下图所示：\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-8.png)\n\n第三个参数：`{String | Array}`\ncreateElement还有第三个参数，这个参数是可选的，可以给其传一个String或Array。比如下面这个小示例：\n\n``` javascript\n<div>\n    <custom-element></custom-element>    \n</div>\n\nVue.component(\'custom-element\', {\n    render: function (createElemen) {\n        var self = this\n\n        return createElement(\n            \'div\', // 第一个参数是一个简单的HTML标签字符 “必选”\n            {\n                class: {\n                    title: true\n                },\n                style: {\n                    border: \'1px solid\',\n                    padding: \'10px\'\n                }\n            }, // 第二个参数是一个包含模板相关属性的数据对象 “可选”\n            [\n                createElement(\'h1\', \'Hello Vue!\'),\n                createElement(\'p\', \'开始学习Vue\')\n            ] // 第三个参数是传了多个子元素的一个数组 “可选”\n        )\n    }\n})\nlet app = new Vue({ el: \'#app\' })\n```\n最终的效果如下：\n![enter description here](https://www.w3cplus.com/sites/default/files/blogs/2018/1804/vue-render-9.png)\n其实从上面这几个小例来看，不难发现，以往我们使用Vue.component()创建组件的方式，都可以用render函数配合createElement来完成。你也会发现，使用Vue.component()和`render`各有所长，正如文章开头的一个示例代码，就不适合Vue.component()的`template`，而使用`render`更方便。\n\n接下来看一个小示例，看看`template`和`render`方式怎么创建相同效果的一个组件:\n\n``` javascript\n<div>\n    <custom-element></custom-element>    \n</div>\n\n\nVue.component(\'custom-element\', {\n    template: `<div id=\'box\' :class=\'{show: show}\' @click=\'handleClick\'></div>`,\n    data() {\n        return {\n            show: true\n        }\n    },\n    methods: {\n        handleClick(){\n            console.log(\'Clicked!\')\n        }\n    }\n})\n```\n\n上面Vue.component()中的代码换成`render`函数之后，可以这样写：\n\n``` javascript\nVue.component(\'custom-element\', {\n    render: function (createElemen) {\n        return createElement(\'div\', {\n            class: {\n                show: this.show\n            },\n            attrs: {\n                id: \'box\'\n            },\n            on: {\n                click: this.handleClick\n            }\n        }, \'Hello Vue!\')\n    },\n    data () {\n        return {\n            show: true\n        }\n    },\n    methods: {\n        handleClick (){\n             console.log(\'Clicked!\')\n        }\n    }\n})\nlet app = new Vue({\n	el: \'#app\'\n}）\n```\n\n### createElement解析过程\n简单的来看一下`createElement`解析的过程，这部分需要对JS有一些功底。不然看起来有点蛋疼：\n\n``` javascript\nconst SIMPLE_NORMALIZE = 1\nconst ALWAYS_NORMALIZE = 2\n\nfunction createElement(context, tag, data, children, normalizationType, alwaysNormalize) {\n    // 兼容不传data的情况\n    if (Array.isArray(data) || isPrimitive(data)) {\n        normalizationType = children\n        children = data\n    }\n\n    // 如果alwaysNormalize是true\n    // 那么normalizationType应该设置为常量 ALWAYS_NORMALIZE 的值\n    if (alwaysNormalize) {\n        normalizationType = ALWAYS_NORMALIZE\n        // 调用_createElement创建虚拟节点\n        return _createElement(context, tag, data, children, normalizationType)\n    }\n\n    function_createElement(context, tag, data, children, normalizationType) {\n        /**\n         * 如果存在data.__ob__，说明data是被Observer观察的数据\n         * 不能用作虚拟节点的data\n         * 需要抛出警告，并返回一个空节点\n         * \n         * 被监控的data不能被用作vnode渲染的数据的原因是：\n         * data在vnode渲染过程中可能会被改变，这样会触发监控，导致不符合预期的操作\n         */\n        if (data && data.__ob__) {\n            process.env.NODE_ENV !== \'production\' && warn(\n                `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\\n` +\n                \'Always create fresh vnode data objects in each render!\',\n                context\n            )\n            return createEmptyVNode()\n        }\n\n        // 当组件的is属性被设置为一个falsy的值\n        // Vue将不会知道要把这个组件渲染成什么\n        // 所以渲染一个空节点\n        if (!tag) return createEmptyVNode()\n\n        // 作用域插槽\n        if (Array.isArray(children) && typeof children[0] === \'function\') {\n            data = data || {}\n            data.scopedSlots = {\n                default: children[0]\n            }\n            children.length = 0\n        }\n\n        // 如果标签名是字符串类型\n        if (typeof tag === \'string\') {\n            let Ctor\n            // 获取标签名的命名空间\n            ns = config.getTagNamespace(tag)\n            // ...略\n        } else {\n            // 当tag不是字符串的时候，我们认为tag是组件的构造类\n            // 所以直接创建\n            vnode = createComponent(tag, data, context, children)\n        }\n\n        if (vnode) {\n            // 如果有namespace，就应用下namespace，然后返回vnode\n            if (ns) applyNS(vnode, ns)\n            return vnode\n        } else {\n            return createEmptyVNode()\n        }\n    }\n}\n```\n> 这部分代码和流程图来自于@JoeRay61的《[Vue原理解析之Virtual DOM](https://segmentfault.com/a/1190000008291645)》一文。\n\n## 使用JavaScript代替模板功能\n在使用Vue模板的时候，我们可以在模板中灵活的使用v-if、v-for、v-model和<slot>之类的。但在render函数中是没有提供专用的API。如果在render使用这些，需要使用原生的JavaScript来实现。\n\n### v-if和v-for\n在render函数中可以使用if/else和map来实现template中的v-if和v-for。\n\n``` html\n<template>\n    <ul v-if=\"items.length\">\n        <li v-for=\"item in items\">{{item}}</li>\n    </ul>\n    <p v-else>No item found.</p>\n</template>\n```\n换成 `render` 函数可以这么写\n\n```html\n<template>\n  <div class=\"hello\">\n      <item-list :items=\"items\"></item-list>\n  </div>\n</template>\n\n<script>\n  export default {\n    components: {\n      \'item-list\': {\n        props: [\'items\'],\n        render: function (createElement){\n          if (this.items.length){\n            return createElement(\'ul\', this.items.map((item)=>{\n              return createElement(\'li\', item)\n            }))\n          } else{\n            return createElement(\'p\', \'No items found.\')\n          }\n        }\n      }\n    },\n    data() {\n      return {\n        items: [\'Javascript\', \'Vue\', \'react\']\n      }\n    }\n  }\n</script>\n```\n### v-model\n`render`函数中也没有与v-model相应的API，如果要实现`v-model`类似的功能，同样需要使用原生JavaScript来实现。\n\n``` javascript\n<template>\n  <div class=\"hello\">\n    <my-input :name=\"name\" @input=\"val => name = val\"></my-input>\n  </div>\n</template>\n\n<script>\n  export default {\n    components: {\n      \'my-input\': {\n        render: function (createElement) {\n          var self = this\n          return createElement(\'input\', {\n            domProps: {\n              value: self.name\n            },\n            on: {\n              input: function (event) {\n                self.$emit(\'input\', event.target.value)\n              }\n            }\n          })\n        },\n        props: {\n          name: String\n        }\n      }\n    },\n    data() {\n      return {\n        name: \'react\'\n      }\n    }\n  }\n</script>\n```\n\n### 插槽\n你可以从this.$slots获取VNodes列表中的静态内容：\n\n``` javascript\nrender: function (createElement){\n    // 相当于 `<div><slot></slot></div>`\n    return createElement(\'div\', this.$slots.default)\n}\n```\n还可以从this.$scopedSlots中获得能用作函数的作用域插槽，这个函数返回VNodes:\n\n``` javascript\nprops: [\'message\'],\nrender: function (createElement){\n    // 相当于 `<div><slot></slot></div>`\n    return createElement(\'div\', [\n        this.$scopeSlots.default({\n            text: this.message\n        })\n    ])\n}\n```\n如果要用渲染函数向子组件中传递作用域插槽，可以利用VNode数据中的scopedSlots域\n\n## JSX\n如果写习惯了template，然后要用render函数来写，一定会感觉好痛苦，特别是面对复杂的组件的时候。不过我们在Vue中使用JSX可以让我们回到更接近于模板的语法上。\n\n``` javascript\n<template>\n  <div class=\"hello\">\n    <my-con></my-con>\n  </div>\n</template>\n\n<script>\n  export default {\n    components: {\n      \'myCon\': {\n        render: function (h) {\n          return (\n            <div>1221</div>\n          )\n        }\n      }\n    }\n  }\n</script>\n```\n> 将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例，实际上也是 JSX 所要求的，如果在作用域中 h 失去作用，在应用中会触发报错。\n\n## 总结\n回过头来看，Vue中的渲染核心关键的几步流程还是非常清晰的：\n\n- new Vue，执行初始化\n- 挂载`$mount`方法，通过自定义`render`方法、`template`、`el`等生成`render`函数\n- 通过Watcher监听数据的变化\n- 当数据发生变化时，`render`函数执行生成VNode对象\n- 通过patch方法，对比新旧`VNode`对象，通过`DOM Diff`算法，添加、修改、删除真正的DOM元素\n\n至此，整个new Vue的渲染过程完毕。\n\n而这篇文章，主要把精力集中在`render`函数这一部分。学习了怎么用`render`函数来创建组件，以及了解了其中`createElement`。\n\n 参考 [大漠 - Vue的render函数](https://www.w3cplus.com/vue/vue-render-function.html)', '2019-02-11 12:43:33', '2019-02-11 12:43:33');
INSERT INTO `article` VALUES (30, '实现简易的 VUE-MVVM', '## 前言\n这是本人的学习的记录，因为最近在准备面试，很多情况下会被提问到：请简述 `mvvm` ?\n一般情况下我可能这么答：`mvvm` 是视图和逻辑的一个分离，是`model view view-model` 的缩写，通过虚拟dom的方式实现双向数据绑定（我随便答得）\n\n那么问题来了，你知道 `mvvm` 是怎么实现的？\n回答: `mvvm` 主要通过 `Object` 的 `defineProperty` 属性，重写 `data` 的 `set` 和`get` 函数来实现。 ok，回答得60分，那么你知道具体实现过程么？想想看，就算他没问到而你答了出来是不是更好？前提下，一定要手撸一下简单的`mvvm`才会对它有印象~\n\n话不多说，接下来是参考自张仁阳老师的教学视频而作，采用的是[ES6](http://es6.ruanyifeng.com/#docs/class)语法，其中也包含了我个人的理解，如果能帮助到您，我将十分高兴。如有错误之处，请各位大佬指正出来，不胜感激~~~\n\n在实现之前，请先了解基本的`mvvm`的编译过程以及使用\n- 编译的流程图\n![](https://user-gold-cdn.xitu.io/2018/7/18/164ac9a02d21c8cb?w=700&h=374&f=jpeg&s=31670)\n\n- 整体分析\n![](https://user-gold-cdn.xitu.io/2018/7/18/164ac90097e68912?w=639&h=388&f=png&s=49631)\n\n可以发现`new MVVM()`后的编译过程主体分为两个部分：\n\n1. 一部分是模板的编译 `Compile`\n    - 编译元素和文本，最终渲染到页面中\n    - 其中标签中有模板指令的标签才执行编译 例如`<div>我很帅</div>` 不执行编译\n2. 一部分是数据劫持 `Observer`\n    - `Dep` 发布订阅，将所有需要通知变化的`data`添加到一个数组中\n    - `Watcher` 如果数据发生改变，在`Object`的`defineProperty`的`set`函数中调用`Watcher`的`update`方法\n\n#### 明确本文需要实现的目标\n1. **实现模板编译的过程 完成`Vue`实例中的属性可以正确绑定在标签中，并且渲染在页面中**\n   -  工作：指令的解析，正则替换双大括号\n   -  将节点的内容`node.textContent`或者`input`的`value`编译出来\n2. **完成数据的双向绑定**\n   - 工作：通过`observe`类劫持数据变化\n   - 添加发布与订阅：`Object.defineProperty` 在`get`钩子中`addSub`,`set`钩子中通知变化`dep.notify()`\n   - `dep.notify()`调用的是`Watcher`的`update`方法，也就是说需要在`input`变化时调用更新\n   \n<!--more-->\n\n##  分解 Vue 实例\n如何入手？首先从怎么使用`Vue`开始。让我们一步步解析`Vue`的使用：\n\n```js\nlet vm = new Vue({\n    el: \'#app\'\n    data: {\n        message: \'hello world\'\n    }\n})\n```\n上面代码可以看出使用`Vue`,我们是先`new` 一个`Vue` 实例，传一个对象参数，包含 `el` 和 `data`。\n\nok，以上得到了信息，接下来让我们实现**目标1**：将`Vue`实例的`data`编译到页面中\n\n## 实现 Complie 编译模板的过程\n先看看页面的使用：`index.html`\n```html\n<div id=\"app\">\n    <input type=\"text\" v-model=\"jsonText.text\">\n    <div>{{message}}</div>\n    {{jsonText.text}}\n</div>\n<script src=\"./watcher.js\"></script>\n<script src=\"./observer.js\"></script>\n<script src=\"./compile.js\"></script>\n<script src=\"./vue.js\"></script>\n<script>\n    let vm = new Vue({\n        el: \'#app\',\n        data: {\n            message: \'gershonv\',\n            jsonText:{\n                text: \'hello Vue\'\n            }\n        }\n    })\n</script>\n```\n\n> 第一步当然是添加`Vue`类作为一个入口文件。\n\n### vue 类-入口文件的添加\n新建一个`vue.js`文件，其代码如下\n构造函数中定义`$el`和`$data`，因为后面的编译要使用到\n\n```js\nclass Vue {\n    constructor(options) {\n        this.$el = options.el; // 挂载\n        this.$data = options.data;\n\n        // 如果有要编译的模板就开始编译\n        if (this.$el) {\n            // 用数据和元素进行编译\n            new Compile(this.$el, this)\n        }\n    }\n}\n```\n- 这里暂时未添加数据劫持`obeserve`，实现目标1暂时未用到，后续再添加\n- 编译需要 `el` 和相关数据，上面代码执行后会有编译，所以我们新建一个执行编译的类的文件\n\n> 这里在入口文件`vue.js`中`new`了一个`Compile`实例，所以接下来新建`compile.js`\n\n### Compile 类-模板编译的添加\n`Compile` 需要做什么？\n我们知道页面中操作`dom`会消耗性能，所以可以把`dom`移入内存处理：\n1. 先把真实的 `dom` 移入到内存中 （在内存中操作`dom`速度比较快）\n    - 怎么放在内存中？可以利用文档碎片 `fragment`\n2. 编译 `compile(fragment){}`\n    - 提取想要的元素节点和文本节点 `v-model` 双大括号，然后进行相关操作。\n3. 把编译好的`fragment`塞回页面里去\n```js\nclass Compile {\n    constructor(el, vm) {\n        this.el = this.isElementNode(el) ? el : document.querySelector(el);\n        this.vm = vm;\n        if (this.el) {// 如果这个元素能获取到 我们才开始编译\n            // 1.先把这些真实的DOM移入到内存中 fragment[文档碎片]\n            let fragment = this.node2fragment(this.el)\n            // 2.编译 => 提取想要的元素节点 v-model 和文本节点 {{}}\n            this.compile(fragment)\n            // 3.编译好的fragment在塞回页面里去\n            this.el.appendChild(fragment)\n        }\n    }\n\n    /* 专门写一些辅助的方法 */\n    isElementNode(node) { // 判断是否为元素及节点，用于递归遍历节点条件\n        return node.nodeType === 1;\n    }\n\n    /* 核心方法 */\n    node2fragment(el) { // 将el的内容全部放入内存中\n        // 文档碎片\n        let fragment = document.createDocumentFragment();\n        let firstChild;\n\n        while (firstChild = el.firstChild) { // 移动DOM到文档碎片中\n            fragment.appendChild(firstChild)\n        }\n        return fragment;\n    }\n    \n    compile(fragment) {\n    }\n}\n```\n> 编译的过程就是把我们的数据渲染好，表现在视图中\n\n#### 编译过程 compile(fragment)\n- 第一步：获取元素的节点，提取其中的指令或者模板双大括号\n    - 首先需要遍历节点，用到了**递归方法**，因为有节点嵌套的关系，`isElementNode` 代表是节点元素，也是递归的终止的判断条件。\n- 第二步：分类编译指令的方法`compileElement` 和 编译文本双大括号的方法\n    - `compileElement` 对`v-model`、`v-text`等指令的解析\n    - `compileText` 编译文本节点 双大括号\n```js\nclass Compile{\n    // ...\n    compile(fragment) {\n        // 遍历节点 可能节点套着又一层节点 所以需要递归\n        let childNodes = fragment.childNodes\n        Array.from(childNodes).forEach(node => {\n            if (this.isElementNode(node)) {\n                // 是元素节点 继续递归\n                // 这里需要编译元素\n                this.compileElement(node);\n                this.compile(node)\n            } else {\n                // 文本节点\n                // 这里需要编译文本\n                this.compileText(node)\n            }\n        })\n    }\n}\n```\n\n##### compileElement && compileText\n1. 取出元素的属性 `node.attributes` 先判断是否包含指令\n2. 判断指令类型(`v-html v-text v-model...`) 调用不一样的数据更新方法\n    - 这里提取了编译的工具对象 `CompileUtil`\n    - 调用方法: `CompileUtil[type](node, this.vm, expr)`\n        - `CompileUtil.类型(节点，实例，v-XX 绑定的属性值)`  \n\n```js\nclass Compile{\n    // ...\n    \n    // 判断是否是指令 ==> compileElement 中递归标签属性中使用\n    isDirective(name) {\n        return name.includes(\'v-\')\n    }\n    \n    compileElement(node) {\n        // v-model 编译\n        let attrs = node.attributes; // 取出当前节点的属性\n        Array.from(attrs).forEach(attr => {\n            let attrName = attr.name;\n            // 判断属性名是否包含 v-\n            if (this.isDirective(attrName)) {\n                // 取到对应的值，放到节点中\n                let expr = attr.value;\n                // v-model v-html v-text...\n                let [, type] = attrName.split(\'-\')\n                CompileUtil[type](node, this.vm, expr);\n            }\n        })\n    }\n    compileText(node) {\n        // 编译 {{}}\n        let expr = node.textContent; //取文本中的内容\n        let reg = /\\{\\{([^}]+)\\}\\}/g;\n        if (reg.test(expr)) {\n            CompileUtil[\'text\'](node, this.vm, expr)\n        }\n    }\n    \n    // compile(fragment){...}\n}\nCompileUtil = {\n    getVal(vm, expr) { // 获取实例上对应的数据\n        expr = expr.split(\'.\'); // 处理 jsonText.text 的情况\n        return expr.reduce((prev, next) => { \n            return prev[next] // 譬如 vm.$data.jsonText.text、vm.$data.message\n        }, vm.$data)\n    },\n    getTextVal(vm, expr) { // 获取文本编译后的结果\n        return expr.replace(/\\{\\{([^}]+)\\}\\}/g, (...arguments) => {\n            return this.getVal(vm, arguments[1])\n        })\n    },\n    text(node, vm, expr) { // 文本处理 参数 [节点, vm 实例, 指令的属性值]\n        let updateFn = this.updater[\'textUpdater\'];\n        let value = this.getTextVal(vm, expr)\n        updateFn && updateFn(node, value)\n    },\n    model(node, vm, expr) { // 输入框处理\n        let updateFn = this.updater[\'modelUpdater\'];\n        updateFn && updateFn(node, this.getVal(vm, expr))\n    },\n    updater: {\n        // 文本更新\n        textUpdater(node, value) {\n            node.textContent = value\n        },\n        // 输入框更新\n        modelUpdater(node, value) {\n            node.value = value;\n        }\n    }\n}\n```\n\n到现在为止 就完成了数据的绑定，也就是说`new Vue` 实例中的 `data` 已经可以正确显示在页面中了，现在要解决的就是**如何实现双向绑定**\n\n结合开篇的`vue`编译过程的图可以知道我们还少一个`observe` 数据劫持，`Dep`通知变化,添加`Watcher`监听变化, 以及最终重写`data`属性\n\n\n## 实现双向绑定\n\n### Observer 类-观察者的添加\n1. 在`vue.js` 中劫持数据\n```js\nclass Vue{\n    //...\n    if(this.$el){\n       new Observer(this.$data); // 数据劫持\n       new Compile(this.$el, this); // 用数据和元素进行编译\n    }  \n}\n```\n2. 新建 `observer.js` 文件\n\n代码步骤：\n- 构造器中添加直接进行 `observe`\n  - 判断`data` 是否存在, 是否是个对象（new Vue 时可能不写`data`属性）\n  - 将数据一一劫持，获取`data`中的`key`和`value`\n```js\nclass Observer {\n    constructor(data) {\n        this.observe(data)\n    }\n\n    observe(data) {\n        // 要对这个数据将原有的属性改成 set 和 get 的形式\n        if (!data || typeof data !== \'object\') {\n            return\n        }\n        // 将数据一一劫持\n        Object.keys(data).forEach(key => {\n            // 劫持\n            this.defineReactive(data, key, data[key])\n            this.observe(data[key]) //递归深度劫持\n        })\n    }\n\n    defineReactive(obj, key, value) {\n        let that = this\n        Object.defineProperty(obj, key, {\n            enumerable: true,\n            configurable: true,\n            get() { // 取值时调用的方法\n                return value\n            },\n            set(newValue) { // 当给data属性中设置的时候，更改属性的值\n                if (newValue !== value) {\n                    // 这里的this不是实例\n                    that.observe(newValue) // 如果是对象继续劫持\n                    value = newValue\n                }\n            }\n        })\n    }\n}\n```\n> 虽然有了`observer`，但是并未关联,以及通知变化。下面就添加`Watcher`类\n\n### Watcher 类的添加\n新建`watcher.js`文件\n- 观察者的目的就是给需要变化的那个元素增加一个观察者，当数据变化后执行对应的方法\n- \n\n先回忆下`watch`的用法：`this.$watch(vm, \'a\', function(){...})`\n我们在添加发布订阅者时需要传入参数有: **vm实例，v-XX绑定的属性, cb回调函数**\n（`getVal` 方法拷贝了之前 `CompileUtil` 的方法，其实可以提取出来的...）\n\n```js\nclass Watcher {\n    // 观察者的目的就是给需要变化的那个元素增加一个观察者，当数据变化后执行对应的方法\n    // this.$watch(vm, \'a\', function(){...})\n    constructor(vm, expr, cb) {\n        this.vm = vm;\n        this.expr = expr;\n        this.cb = cb;\n\n        // 先获取下老的值\n        this.value = this.get();\n    }\n\n    getVal(vm, expr) { // 获取实例上对应的数据\n        expr = expr.split(\'.\');\n        return expr.reduce((prev, next) => { //vm.$data.a\n            return prev[next]\n        }, vm.$data)\n    }\n\n    get() {\n        let value = this.getVal(this.vm, this.expr);\n        return value\n    }\n\n    // 对外暴露的方法\n    update(){\n        let newValue = this.getVal(this.vm, this.expr);\n        let oldValue = this.value\n\n        if(newValue !== oldValue){\n            this.cb(newValue); // 对应 watch 的callback\n        }\n    }\n}\n\n```\n`Watcher` 定义了但是还没有调用，模板编译的时候，需要调观察的时候观察一下\n`Compile`\n```js\nclass Compile{\n    //...\n}\nCompileUtil = {\n    //...\n    text(node, vm, expr) { // 文本处理 参数 [节点, vm 实例, 指令的属性值]\n        let updateFn = this.updater[\'textUpdater\'];\n        let value = this.getTextVal(vm, expr)\n        updateFn && updateFn(node, value)\n\n        expr.replace(/\\{\\{([^}]+)\\}\\}/g, (...arguments) => {\n            new Watcher(vm, arguments[1], () => {\n                // 如果数据变化了，文本节点需要重新获取依赖的属性更新文本中的内容\n                updateFn && updateFn(node, this.getTextVal(vm, expr))\n            })\n        })\n    },\n    //...\n    model(node, vm, expr) { // 输入框处理\n        let updateFn = this.updater[\'modelUpdater\'];\n        // 这里应该加一个监控，数据变化了，应该调用watch 的callback\n        new Watcher(vm, expr, (newValue) => {\n            // 当值变化后会调用cb 将newValue传递过来（）\n            updateFn && updateFn(node, this.getVal(vm, expr))\n        });\n\n        node.addEventListener(\'input\', e => {\n            let newValue = e.target.value;\n            this.setVal(vm, expr, newValue)\n        })\n        updateFn && updateFn(node, this.getVal(vm, expr))\n    },\n    \n    //...\n}\n```\n实现了监听后发现变化并没有通知到所有指令绑定的模板或是双大括号，所以我们需要`Dep` 监控、实例的发布订阅属性的一个类，我们可以添加到`observer.js`中\n\n### Dep 类的添加\n注意 第一次编译的时候不会调用`Watcher`，`dep.target`不存在,`new Watcher`的时候`target`才有值 \n有点绕，看下面代码：\n```js\nclass Watcher {\n    constructor(vm, expr, cb) {\n        //...\n        this.value = this.get()\n    }\n    get(){\n        Dep.target = this;\n        let value = this.getVal(this.vm, this.expr);\n        Dep.target = null;\n        return value\n    }\n    //...\n}\n\n// compile.js\nCompileUtil = {\n    model(node, vm, expr) { // 输入框处理\n        //...\n        new Watcher(vm, expr, (newValue) => {\n            // 当值变化后会调用cb 将newValue传递过来（）\n            updateFn && updateFn(node, this.getVal(vm, expr))\n        });\n    }\n}\n```\n\n```js\nclass Observer{\n    //...\n    defineReactive(obj, key, value){\n        let that = this;\n        let dep = new Dep(); // 每个变化的数据 都会对应一个数组，这个数组存放所有更新的操作\n        Object.defineProperty(obj, key, {\n            //...\n            get(){\n                Dep.target && dep.addSub(Dep.target)\n                //...\n            }\n             set(newValue){\n                 if (newValue !== value) {\n                    // 这里的this不是实例\n                    that.observe(newValue) // 如果是对象继续劫持\n                    value = newValue;\n                    dep.notify(); //通知所有人更新了\n                }\n             }\n        })\n    }\n}\nclass Dep {\n    constructor() {\n        // 订阅的数组\n        this.subs = []\n    }\n\n    addSub(watcher) {\n        this.subs.push(watcher)\n    }\n\n    notify() {\n        this.subs.forEach(watcher => watcher.update())\n    }\n}\n```\n\n以上代码 就完成了**发布订阅者**模式,简单的实现。。也就是说双向绑定的目标2已经完成了\n\n---\n## 结语\n板门弄斧了，本人无意哗众取宠，这只是一篇我的学习记录的文章。想分享出来，这样才有进步。\n如果这篇文章帮助到您，我将十分高兴。有问题可以提`issue`，有错误之处也希望大家能提出来，非常感激。\n\n具体源码我放在了我的github了，有需要的自取。\n[源码链接](https://github.com/gershonv/my-code-store)', '2019-02-11 12:44:10', '2019-02-11 12:44:10');
INSERT INTO `article` VALUES (31, 'v-model 浅析', '## v-model 介绍\nv-model 只是个语法糖，用于实现数据的双向绑定，实现的原理为 \n\n- v-bind 绑定value\n- v-on 监听 input 事件 绑定的值发生改变时触发 重新复制给value\n\n如果不理解，请看组件之间的通信。接下来看一段代码\n\n``` html\n<input v-model=\"searchText\">\n\n<!--相当于以下代码-->\n<input :value=\"searchText\" @input=\"searchText = $event.target.value\">\n```\n\n同理我们在封装组件时可以利用这个原理 构造 v-model 语法糖，下例我们封装一个my-input 组件：\n\n<!--more-->\n\n``` html\n<template>\n  <input :value=\"content\" @input=\"handleChange($event)\">\n</template>\n\n<script>\n  export default {\n    props: {\n      value: String\n    },\n    data() {\n      return {\n        content: this.value\n      }\n    },\n    methods: {\n      handleChange($event) {\n        this.$emit(\'input\',  $event.target.value)\n      }\n    }\n  }\n</script>\n```\nok，简单的封装完成。说明一点，props 的类型为**基本类型** 如 `String`、`Number`、`Boolean` 等在my-input 组件是不允许修改，而 props 类型为Object、Array等**引用类型**时可以修改，但是此时数据不再是单向流动。也就是说，你在修改props时也会修改到父组件中的值，简单一句话概括：**父子组件数据双向绑定**。\n\n### .sync 实现父子组件间的双向数据绑定\n> 在有些情况下，我们可能需要对一个 prop 进行“双向绑定”。不幸的是，真正的双向绑定会带来维护上的问题，因为子组件可以修改父组件，且在父组件和子组件都没有明显的改动来源。\n> 这也是为什么我们推荐以 update:my-prop-name 的模式触发事件取而代之。接着上个例子，在一个包含 value prop 的假设的组件中，我们可以用以下方法表达对其赋新值的意图：\n```javascript\nthis.$emit(\'update:value\', newValue)\n```\n然后父组件可以监听那个事件并根据需要更新一个本地的数据属性。例如：\n```html\n<my-input\n  :value=\"searchText\"\n  @update:value=\"searchText = $event\"></my-input>\n\n<!--等同于-->\n<my-input :value.sync=\"searchText\"></my-input>\n```\nmy-input 组件的代码\n```html\n<template>\n  <input :value=\"value\" @input=\"handleChange($event)\">\n</template>\n\n<script>\n  export default {\n    props: {\n      value: String\n    },\n    methods: {\n      handleChange($event) {\n        this.$emit(\'update:value\', $event.target.value)\n      }\n    }\n  }\n</script>\n```\n\n\n\n## 自定义组件的 v-model\n一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件，但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突：\n\n自定义单选框：\n```html\n<template>\n  <div>\n    <base-checkbox v-model=\"isCheck\"></base-checkbox> {{isCheck}}\n  </div>\n</template>\n\n<script>\n  export default {\n    components: {\n      \'base-checkbox\': {\n        model: {\n          prop: \'checked\',\n          event: \'change\'\n        },\n        props: {\n          checked: Boolean\n        },\n        template: `<input\n                    type=\"checkbox\"\n                    :checked=\"checked\"\n                    @change=\"$emit(\'change\', $event.target.checked)\">`\n      }\n    },\n    data() {\n      return {\n        isCheck: true\n      }\n    },\n  }\n</script>\n```\n\n>这里的 isCheck 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候，这个 isCheck 的属性将会被更新。', '2019-02-11 12:44:35', '2019-02-11 12:44:35');
INSERT INTO `article` VALUES (32, '封装组件系列 - el 分页表格', '## 前言\n\n本次封装基于 `antd` 风格, 实现高度可配置的表格封装配置。本来想通过 `vue.extends` 去封装的，奈何几个月没写过 `vue` ，而且对 `vue` 的 `extends` 不熟悉所以放弃了...\n\n之前有小伙伴确实引用了我的代码，发现封装出现了一些纰漏，对此十分抱歉，之前封装的太仓促了。几个月前的代码，现在重新封装又有了新的体会。\n\n更新时间 【2018.11.09】，效果如下：\n\n![](https://user-gold-cdn.xitu.io/2018/11/9/166f7e2fa283341b?w=1896&h=761&f=png&s=84208)\n\n## API 说明\n\n- `columns` : **必选**, 列描述数据对象， Array\n- `dataSource` : **必选**, 数据数组\n- `options` : **必选**, 表格参数控制, maxHeight、stripe 等等..\n- `fetch` : 获取数据的 Function\n- `pagination` : 分页信息，不传则不显示分页\n- `row-click` ：当某一行被点击时会触发该事件\n- `selection-change` : 当选择项发生变化时会触发该事件\n- 其他的 api 可以自行添加\n\n其他说明我在代码注释中写的很清楚了，请自行查看。\n\n根据条件渲染: 只通过 `render` 去判断参数不同而渲染不一样的表格数据。 `render` 函数可以渲染任何你想要的组件\n\n值得注意的是，`this` 对象的绑定不要出错了,如果需要更多增强的功能，各位可以自行添加...\n\n<!--more-->\n\n## Home.vue 组件\n\n```html\n<template>\n    <div>\n      <h2>Home</h2>\n      <CommonTable\n        :columns=\"columns\"\n        :dataSource=\"tableData\"\n        :options=\"options\"\n        :fetch=\"fetchTableData\"\n        :pagination=\"pagination\"\n        @row-click=\"handleRowClick\"\n        @selection-change=\"handleSelectionChange\"\n        />\n    </div>\n</template>\n\n<script>\nimport axios from \'axios\'\nimport CommonTable from \'../components/Table\'\n\nexport default{\n  components:{\n    CommonTable\n  },\n  data(){\n    return {\n      columns: [\n         {\n          prop: \'id\',\n          label: \'编号\',\n          width: 60\n        },\n        {\n          prop: \'title\',\n          label: \'标题\',\n          // render 可以根据你想要渲染的方式渲染\n          // jsx 不提供 v-model 指令，若你想要使用，，推荐使用插件 babel-plugin-jsx-v-model\n          // jsx https://github.com/vuejs/babel-plugin-transform-vue-jsx\n          render: (row, index) => {\n            return (\n              <span style=\"color: blue\" onClick={e => this.handleClick(e, row)}>{row.title}</span>\n            )\n          }\n        },\n        {\n          prop: \'author\',\n          label: \'作者\'\n        },\n        {\n          button: true,\n          label: \'按钮组\',\n          group: [{\n            // you can props => type size icon disabled plain\n            name: \'编辑\',\n            type: \'warning\',\n            icon: \'el-icon-edit\',\n            plain: true,\n            onClick: (row, index) => { // 箭头函数写法的 this 代表 Vue 实例\n              console.log(row, index)\n            }\n          }, {\n            name: \'删除\',\n            type: \'danger\',\n            icon: \'el-icon-delete\',\n            disabled: false,\n            onClick(row) { // 这种写法的 this 代表 group 里的对象\n              this.disabled = true\n              console.log(this)\n            }\n          }]\n        }\n      ],\n      tableData: [\n        {\n          id: 1,\n          title: \'标题1\',\n          author: \'郭大大\'\n        },\n        {\n          id: 2,\n          title: \'标题2\',\n          author: \'郭大大2\'\n        }\n      ],\n      pagination: {\n        total: 0,\n        pageIndex: 1,\n        pageSize: 15\n      },\n      options: {\n        mutiSelect: true,\n        index: true, // 显示序号， 多选则 mutiSelect\n        loading: false, // 表格动画\n        initTable: true, // 是否一挂载就加载数据\n      }\n    }\n  },\n  methods: {\n    handleClick(e, row){\n      //transform-vue-jsx 的nativeOnClick 失效 , 所以采用 event.cancelBubble 控制点击事件的冒泡... 如果点击事件不影响你的点击行事件，可以不传\n      e.cancelBubble = true // 停止冒泡，否则会触发 row-click\n      console.log(row)\n    },\n    fetchTableData() {\n       this.options.loading = true\n       axios.post(\'https://www.easy-mock.com/mock/5b3f80edfa972016b39fefbf/example/tableData\', {\n        pageIndex: this.pagination.pageIndex,\n        pageSize: this.pagination.pageSize\n      }).then(res => {\n        const { list, total } = res.data.data\n        this.tableData = list\n        this.pagination.total = total\n        this.options.loading = false\n      }).catch((error) => {\n        console.log(error)\n        this.options.loading = false\n      })\n    },\n    handleRowClick(row, event, column){ // 点击行的事件，同理可以绑定其他事件\n      console.log(\'click row:\',row, event, column)\n    },\n    handleSelectionChange(selection){\n      console.log(selection)\n    }\n  }\n}\n</script>\n```\n\n## Table.vue 组件\n\n```html\n<template>\n  <div>\n    <el-table\n      v-loading=\"options.loading\"\n      :data=\"dataSource\"\n      :max-height=\"options.maxHeight\"\n      :stripe=\"options.stripe\"\n      :border=\"options.border\"\n      @row-click=\"handleRowClick\"\n      @selection-change=\"handleSelectionChange\"\n      header-row-class-name=\"table-header-row\">\n\n      <!--selection选择框-->\n      <el-table-column v-if=\"options.mutiSelect\" type=\"selection\" style=\"width:50px\" align=\"center\"></el-table-column>\n\n      <!--序号-->\n      <el-table-column v-if=\"options.index\" label=\"序号\" type=\"index\" width=\"50\" align=\"center\"></el-table-column>\n\n      <!--数据列-->\n      <template v-for=\"(column, index) in columns\">\n        <el-table-column\n          :key=\"index\"\n          :prop=\"column.prop\"\n          :label=\"column.label\"\n          :align=\"column.align||\'center\'\"\n          :width=\"column.width\"\n          :fixed=\"column.fixed\">\n          <template slot-scope=\"scope\">\n\n            <template v-if=\"!column.render\">\n              {{scope.row[column.prop]}}\n            </template>\n\n             <!-- render -->\n            <template v-else>\n              <RenderDom :row=\"scope.row\" :index=\"index\" :render=\"column.render\" />\n            </template>\n\n            <!-- render button -->\n            <template v-if=\"column.button\">\n              <template v-for=\"(btn, i) in column.group\">\n                <el-button\n                  :key=\"i\"\n                  :type=\"btn.type\" :size=\"btn.size || \'mini\'\" :icon=\"btn.icon\" :disabled=\"btn.disabled\" :plain=\"btn.plain\"\n                   @click.stop=\"btn.onClick(scope.row, scope.$index)\"\n                  >{{btn.name}}</el-button>\n              </template>\n            </template>\n\n            <!-- slot 你可以其他常用项 -->\n\n          </template>\n\n        </el-table-column>\n      </template>\n\n    </el-table>\n\n     <!-- 分页 -->\n    <el-pagination\n        v-if=\"pagination\"\n        :total=\"pagination.total\"\n        :page-sizes=\"[20, 50, 100, 500, 5000]\"\n        layout=\"total, sizes, prev, pager, next, jumper\"\n        @size-change=\"handleSizeChange\"\n        @current-change=\"handleIndexChange\"\n        style=\"margin-top: 20px;text-align: right\"\n    ></el-pagination>\n\n  </div>\n</template>\n\n<script>\n  export default {\n    components: {\n      RenderDom: {\n        functional: true, // 函数式组件 - 无 data 和 this 上下文 => better render\n        props: {\n          row: Object,\n          index: Number,\n          render: Function\n        },\n        /**\n         * @param {Function} createElement - 原生创建dom元素的方法， 弃用，推荐使用 jsx\n         * @param {Object} ctx - 渲染的节点的this对象\n         * @argument 传递参数 row index\n         */\n        render(createElement, ctx){\n          const { row, index } = ctx.props\n          return ctx.props.render(row, index)\n        }\n      }\n    },\n    props:{\n      dataSource: Array,\n      options: Object,   // 表格参数控制 maxHeight、stripe 等等...\n      columns: Array,\n      fetch: Function,   // 获取数据的函数\n      pagination: Object // 分页，不传则不显示\n    },\n    created() {\n      // 传入的options覆盖默认设置\n      this.$parent.options = Object.assign({\n          maxHeight: 500,\n          stripe: true, // 是否为斑马纹\n          border: true\n      }, this.options)\n\n      this.options.initTable && this.fetch()\n    },\n    methods: {\n      handleSizeChange(size) { // 切换每页显示的数量\n        this.pagination.pageSize = size\n        this.fetch()\n      },\n      handleIndexChange(current) { // 切换页码\n        this.pagination.pageIndex = current\n        this.fetch()\n      },\n      handleSelectionChange(selection) {\n        this.$emit(\'selection-change\', selection)\n      },\n      handleRowClick(row, event, column) {\n        this.$emit(\'row-click\', row, event, column)\n      }\n    }\n  }\n</script>\n\n<style>\n.el-table th,\n.el-table tr.table-header-row {\n  background: #e5c5d2; /* 示例， 对表格样式上的修饰 */\n}\n</style>\n```\n\n## 结语\n\n上述代码封装完整性可能不是这么高，但思路在呢，如果需要更多配置，各位可以在进行加强...\n\n吐槽一下，本来是想 `props` 数据来重写 `table` 参数，类似 `react`:\n\n```jsx\n<Home>\n  <ComonTable {...props} >\n</Home>\n\n// ComonTable\n<el-table {...props.options}>\n</el-table>\n```\n\n所以想到继承，自己又不熟悉。 而且发现 `vue` 展开绑定多个属性是不可以的： 可能是我没 `google` 到。如果可以，请大佬告知一声，谢谢\n\n[jsx 语法快速入门](https://github.com/vuejs/babel-plugin-transform-vue-jsx)', '2019-02-11 12:45:03', '2019-02-11 12:45:03');
INSERT INTO `article` VALUES (33, 'ES6 - async await', '## async 函数\n\n> `async` 函数是什么？一句话，它就是 `Generator` 函数的语法糖。`async` 函数就是将 `Generator` 函数的星号（\\*）替换成 `async`，将 `yield` 替换成 `await`，仅此而已\n\n```js\nfunction getSomething() {\n  return \'something\'\n}\n\nasync function testAsync() {\n  return \'Hello async\'\n}\n\nasync function test() {\n  const v1 = await getSomething() // 普通值\n  const v2 = await testAsync() // Promise对象\n  console.log(v1, v2)\n}\n\ntest()\n  .then(() => {\n    console.log(\'调用该函数时，会立即返回一个Promise对象\')\n  })\n  .catch(e => {})\n```\n\n<!--more-->\n\n## async 函数的实现原理\n\n`async` 函数的实现原理，就是将 `Generator` 函数和自动执行器，包装在一个函数里。\n\n```js\nasync function fn(args) {\n  // ...\n}\n\n// 等同于\n\nfunction fn(args) {\n  return spawn(function*() {\n    // ...\n  })\n}\n```\n\n所有的 `async` 函数都可以写成上面的第二种形式，其中的 `spawn` 函数就是自动执行器。\n下面给出 `spawn` 函数的实现，基本就是前文自动执行器的翻版。\n\n```js\nfunction spawn(genF) {\n  return new Promise(function(resolve, reject) {\n    const gen = genF()\n    let next\n    function step(nextF) {\n      let next\n      try {\n        next = nextF()\n      } catch (err) {\n        return reject(err)\n      }\n      if (next.done) return resolve(next.value)\n      Promise.resolve(next.value).then(\n        v => step(() => gen.next(v)), \n        e => gen.throw(e)\n      )\n    }\n    step(() => gen.next(undefined))\n  })\n}\n```', '2019-02-11 12:47:16', '2019-02-11 12:47:16');
INSERT INTO `article` VALUES (34, 'ES6 - Class', '## 简介\n\nJavaScript 语言中，生成实例对象的传统方法是通过构造函数。下面是一个例子。\n\n```js\nfunction Point(x, y) {\n  this.x = x\n  this.y = y\n}\n\nPoint.prototype.toString = function() {\n  return \'(\' + this.x + \', \' + this.y + \')\'\n}\n\nvar p = new Point(1, 2)\n```\n\nES6 提供了更接近传统语言的写法，引入了 Class（类）这个概念，作为对象的模板。通过`class`关键字，可以定义类。\n\n```js\n//定义类\nclass Point {\n  constructor(x, y) {\n    this.x = x // this 代表实例对象\n    this.y = y\n  }\n\n  toString() {\n    return \'(\' + this.x + \', \' + this.y + \')\'\n  }\n}\n```\n\n<!--more-->\n\n- `constructor`: 构造方法，类的默认方法，通过 new 命令生成对象实例时，自动调用该方法。\n  - 一个类必须有 constructor 方法，如果没有显式定义，一个空的 constructor 方法会被默认添加。\n- `this`: 关键对象\n\n> 定义“类”的方法的时候，前面不需要加上 function 这个关键字，直接把函数定义放进去了就可以了。另外，方法之间不需要逗号分隔，加了会报错。\n\nES6 的类，完全可以看作构造函数的另一种写法。\n\n```js\nclass Point {\n  // ...\n}\n\ntypeof Point // \"function\" ==> 类的数据类型就是函数\nPoint === Point.prototype.constructor // true ==> 类本身就指向构造函数\n\n// 定义与使用\nclass Bar {\n  doStuff() {\n    console.log(\'stuff\')\n  }\n}\n\nvar b = new Bar() // new 默认执行Bar类的 constructor 方法，该方法默认返回实例对象 即this\nb.doStuff() // \"stuff\"\n```\n\n构造函数的`prototype`属性，在 ES6 的“类”上面继续存在。事实上，类的所有方法都定义在类的`prototype`属性上面。\n\n```js\nclass Point {\n  constructor() {\n    // ...\n  }\n\n  toString() {\n    // ...\n  }\n\n  toValue() {\n    // ...\n  }\n}\n\n// 等同于\nPoint.prototype = {\n  constructor() {},\n  toString() {},\n  toValue() {}\n}\n```\n\n在类的实例上面调用方法，其实就是调用原型上的方法。\n\n```js\nclass B {}\nlet b = new B()\n\nb.constructor === B.prototype.constructor // true\n```\n\n由于类的方法都定义在`prototype`对象上面，所以类的新方法可以添加在`prototype`对象上面。Object.assign 方法可以很方便地一次向类添加多个方法。\n\n```js\nclass Point {\n  constructor() {\n    // ...\n  }\n}\n\nObject.assign(Point.prototype, {\n  toString() {},\n  toValue() {}\n})\n\n// prototype对象的constructor属性，直接指向“类”的本身，这与 ES5 的行为是一致的\nPoint.prototype.constructor === Point // true\n\nObject.keys(Point.prototype) // [] ==> 类的内部所有定义的方法，都是不可枚举的（non-enumerable）这一点与 ES5 的行为不一致\n```\n\n类的属性名，可以采用表达式。\n\n```js\nlet methodName = \'getArea\'\n\nclass Square {\n  constructor(length) {\n    // ...\n  }\n\n  [methodName]() {\n    // ...\n  }\n}\n```\n\n## 类的实例对象\n\n与 ES5 一样，实例的属性除非显式定义在其本身（即定义在`this`对象上），否则都是定义在原型上（即定义在`class`上）。\n\n```js\n//定义类\nclass Point {\n  constructor(x, y) {\n    this.x = x\n    this.y = y\n  }\n\n  toString() {\n    return \'(\' + this.x + \', \' + this.y + \')\'\n  }\n}\n\nvar point = new Point(2, 3)\n\npoint.toString() // (2, 3)\n\npoint.hasOwnProperty(\'x\') // true\npoint.hasOwnProperty(\'y\') // true\npoint.hasOwnProperty(\'toString\') // false\npoint.__proto__.hasOwnProperty(\'toString\') // true\n```\n\n`hasOwnProperty`: 查找对象原型上是否有某属性 （上面代码表示 `toString` 保存在`Point`类中，point 是通过原型链获得 `toString` 方法）\n\n## Class 表达式\n\n与函数一样，类也可以使用表达式的形式定义。\n\n```js\nconst MyClass = class Me {\n  getClassName() {\n    return Me.name // 内部可以使用到这个类Me\n  }\n}\n\n// 这个类的名字是MyClass而不是Me，Me只在 Class 的内部代码可用，指代当前类\nlet inst = new MyClass()\ninst.getClassName() // Me\nMe.name // ReferenceError: Me is not defined\n```\n\n如果类的内部没用到的话，可以省略`Me`，也就是可以写成下面的形式。\n\n```js\nconst MyClass = class {\n  /* ... */\n}\n```\n\n采用 Class 表达式，可以写出立即执行的 Class。\n\n```js\nlet person = new class {\n  constructor(name) {\n    this.name = name\n  }\n\n  sayName() {\n    console.log(this.name)\n  }\n}(\'张三\')\n\nperson.sayName() // \"张三\"\n```\n\n## 私有方法和私有属性\n\n私有方法是常见需求，但 `ES6` 不提供，只能通过变通方法模拟实现。\n\n在命名上加以区别:\n\n```js\nlass Widget {\n\n  // 公有方法\n  foo (baz) {\n    this._bar(baz);\n  }\n\n  // 私有方法\n  _bar(baz) {\n    return this.snaf = baz;\n  }\n\n  // ...\n}\n// 不保险的，在类的外部，还是可以调用到这个方法\n```\n\n将私有方法移出模块，因为模块内部的所有方法都是对外可见的:\n\n```js\nclass Widget {\n  foo(baz) {\n    bar.call(this, baz)\n  }\n\n  // ...\n}\n\nfunction bar(baz) {\n  return (this.snaf = baz)\n}\n```\n\n上面代码中，foo 是公有方法，内部调用了 bar.call(this, baz)。这使得 bar 实际上成为了当前模块的私有方法。\n\n利用`Symbol`值的唯一性，将私有方法的名字命名为一个`Symbol`值:\n\n```js\nonst bar = Symbol(\'bar\');\nconst snaf = Symbol(\'snaf\');\n\nexport default class myClass{\n\n  // 公有方法\n  foo(baz) {\n    this[bar](baz);\n  }\n\n  // 私有方法\n  [bar](baz) {\n    return this[snaf] = baz;\n  }\n\n  // ...\n};\n```\n\n上面代码中，bar 和 snaf 都是 Symbol 值，导致第三方无法获取到它们，因此达到了私有方法和私有属性的效果。\n\n## this 的指向\n\n类的方法内部如果含有`this`，它默认指向类的实例。但是，必须非常小心，一旦单独使用该方法，很可能报错。\n\n```js\nclass Logger {\n  printName(name = \'there\') {\n    // this 默认指向 Logger\n    this.print(`Hello ${name}`)\n  }\n\n  print(text) {\n    console.log(text)\n  }\n}\n\nconst logger = new Logger()\nconst { printName } = logger\nprintName() // TypeError: Cannot read property \'print\' of undefined\n// this会指向该方法运行时所在的环境，因为找不到print方法而导致报错。\n```\n\n解决办法\n\n- 在构造方法中绑定`this`\n\n```js\nclass Logger {\n  constructor() {\n    this.printName = this.printName.bind(this)\n  }\n\n  // ...\n}\n```\n\n- 箭头函数\n\n```js\nclass Logger {\n  constructor() {\n    this.printName = (name = \'there\') => {\n      this.print(`Hello ${name}`)\n    }\n  }\n  // ...\n}\n```\n\n- 使用`Proxy`，获取方法的时候，自动绑定`this`\n\n```js\nunction selfish (target) {\n  const cache = new WeakMap();\n  const handler = {\n    get (target, key) {\n      const value = Reflect.get(target, key);\n      if (typeof value !== \'function\') {\n        return value;\n      }\n      if (!cache.has(value)) {\n        cache.set(value, value.bind(target));\n      }\n      return cache.get(value);\n    }\n  };\n  const proxy = new Proxy(target, handler);\n  return proxy;\n}\n\nconst logger = selfish(new Logger());\n```\n\n## getter setter\n\n与 ES5 一样，在“类”的内部可以使用`get`和`set`关键字，对某个属性设置存值函数和取值函数，拦截该属性的存取行为。\n\n```js\nclass MyClass {\n  constructor() {\n    // ...\n  }\n  get prop() {\n    return \'getter\'\n  }\n  set prop(value) {\n    console.log(\'setter: \' + value)\n  }\n}\n\nlet inst = new MyClass()\n\ninst.prop = 123\n// setter: 123\n\ninst.prop\n// \'getter\'\n```\n\n存值函数和取值函数是设置在属性的 Descriptor 对象上的\n\n## Class 的 Generator 方法\n\ntodo // 对 Generator 不熟悉，待下次理解了在写\n\n## Class 的静态方法\n\n静态方法：不会被实例继承，而是直接通过类来调用。\n\n> 类相当于实例的原型，所有在类中定义的方法，都会被实例继承。如果在一个方法前，加上 static 关键字，就表示该方法不会被实例继承，而是直接通过类来调用\n\n```js\nclass Foo {\n  static classMethod() {\n    return \'hello\'\n  }\n}\n\nFoo.classMethod() // \'hello\'\n\nvar foo = new Foo()\nfoo.classMethod()\n// TypeError: foo.classMethod is not a function\n```\n\n> 注意，如果静态方法包含`this`关键字，这个`this`指的是类，而不是实例。\n\n```js\nclass Foo {\n  static bar() {\n    this.baz()\n  }\n  static baz() {\n    console.log(\'hello\')\n  }\n  baz() {\n    console.log(\'world\')\n  }\n}\n\nFoo.bar() // hello\n```\n\n**父类的静态方法，可以被子类继承。**\n\n```js\nclass Foo {\n  static classMethod() {\n    return \'hello\'\n  }\n}\n\nclass Bar extends Foo {}\n\nBar.classMethod() // \'hello\'\n```\n\n**静态方法也是可以从`super`对象上调用的**\n\n```js\nclass Foo {\n  static classMethod() {\n    return \'hello\'\n  }\n}\n\nclass Bar extends Foo {\n  static classMethod() {\n    return super.classMethod() + \', too\'\n  }\n}\n\nBar.classMethod() // \"hello, too\"\n```\n\n## Class 的静态属性和实例属性\n\n静态属性：`Class` 本身的属性，即`Class.propName`，而不是定义在实例对象（`this`）上的属性。\n\n```js\nclass Foo {}\n\nFoo.prop = 1\nFoo.prop // 1\n```\n\n> ES6 明确规定，Class 内部只有静态方法，没有静态属性\n\n写法无效如下：\n\n```js\n// 以下两种写法都无效\nclass Foo {\n  // 写法一\n  prop: 2\n\n  // 写法二\n  static prop: 2\n}\n\nFoo.prop // undefined\n```\n\n类的实例属性\n类的实例属性可以用等式，写入类的定义之中\n\n```js\nclass MyClass {\n  myProp = 42\n\n  constructor() {\n    console.log(this.myProp) // 42\n  }\n}\n```\n\n类的静态属性\n类的静态属性只要在上面的实例属性写法前面，加上`static`关键字就可以了。\n\n```js\nclass MyClass {\n  static myStaticProp = 42\n\n  constructor() {\n    console.log(MyClass.myStaticProp) // 42\n  }\n}\nvar p = new MyClass() // p 中实例属性没有 myStaticProp\n```', '2019-02-11 12:47:49', '2019-02-11 12:47:49');
INSERT INTO `article` VALUES (35, 'ES6 - generator', '## 基本概念\n\n> `Generator` 函数是 ES6 提供的一种异步编程解决方案。语法上，首先可以把它理解成，`Generator` 函数是一个状态机，封装了多个内部状态。\n\n形式上，`Generator` 函数是一个普通函数，但是有两个特征。一是，`function` 关键字与函数名之间有一个星号；二是，函数体内部使用 `yield` 表达式，定义不同的内部状态。\n\n```js\n// 该函数有三个状态：hello，world 和 return 语句（结束执行）\nfunction* helloWorldGenerator() {\n  yield \'hello\'\n  yield \'world\'\n  return \'ending\'\n}\n\nvar hw = helloWorldGenerator()\n\n// 只有调用 next() 函数才会执行\n\nhw.next() // { value: \'hello\', done: false }\nhw.next() // { value: \'world\', done: false }\nhw.next() // { value: \'ending\', done: true }\nhw.next() // { value: undefined, done: true }\n\nhw // {}\n\n// yield表达式如果用在另一个表达式之中，必须放在圆括号里面\nfunction* demo() {\n  console.log(\'Hello\' + yield) // SyntaxError\n  console.log(\'Hello\' + yield 123) // SyntaxError\n\n  console.log(\'Hello\' + (yield)) // OK\n  console.log(\'Hello\' + (yield 123)) // OK\n}\n```\n\n`Generator` 函数的调用方法与普通函数一样，也是在函数名后面加上一对圆括号\n不同的是，调用 `Generator` 函数后，该函数并不执行，返回的也不是函数运行结果，而是一个指向内部状态的指针对象\n\n总结一下：\n\n- 调用 `Generator` 函数，返回一个遍历器对象，代表 `Generator` 函数的内部指针。\n- 以后，每次调用遍历器对象的 next 方法，就会返回一个有着 `value` 和 `done` 两个属性的对象。\n  - `value` 属性表示当前的内部状态的值，是 yield 表达式后面那个表达式的值\n  - `done` 属性是一个布尔值，表示是否遍历结束。', '2019-02-11 12:48:19', '2019-02-11 12:48:19');
INSERT INTO `article` VALUES (36, 'ES6 - Map 和 Set', '## Map 和 Set\n\nJavaScript 的默认对象表示方式`{}`可以视为其他语言中的`Map`或`Dictionary`的数据结构，即一组键值对。\n\n但是 JavaScript 的对象有个小问题，就是键必须是字符串。但实际上 Number 或者其他数据类型作为键也是非常合理的。\n\n### Map\n\n`Map`是一组键值对的结构，具有极快的查找速度。\n\n举个例子，假设要根据同学的名字查找对应的成绩，如果用`Array`实现，需要两个`Array`：\n\n```js\nvar names = [\'Michael\', \'Bob\', \'Tracy\']\nvar scores = [95, 75, 85]\n```\n\n给定一个名字，要查找对应的成绩，就先要在 names 中找到对应的位置，再从 scores 取出对应的成绩，Array 越长，耗时越长。\n\n如果用 Map 实现，只需要一个“名字”-“成绩”的对照表，直接根据名字查找成绩，无论这个表有多大，查找速度都不会变慢。用 JavaScript 写一个 Map 如下：\n\n<!--more-->\n\n```js\nvar m = new Map([[\'Michael\', 95], [\'Bob\', 75], [\'Tracy\', 85]])\nm.get(\'Michael\') // 95\n```\n\n初始化`Map`需要一个二维数组，或者直接初始化一个空`Map`。`Map`具有以下方法：\n\n```js\nvar m = new Map() // 空Map\nm.set(\'Adam\', 67) // 添加新的key-value\nm.set(\'Bob\', 59)\nm.has(\'Adam\') // 是否存在key \'Adam\': true\nm.get(\'Adam\') // 67\nm.delete(\'Adam\') // 删除key \'Adam\'\nm.get(\'Adam\') // undefined\n```\n\n由于一个 key 只能对应一个 value，所以，多次对一个 key 放入 value，后面的值会把前面的值冲掉：\n\n```js\nvar m = new Map()\nm.set(\'Adam\', 67)\nm.set(\'Adam\', 88)\nm.get(\'Adam\') // 88\n```\n\n### Set\n\n`Set`和`Map`类似，也是一组 key 的集合，但不存储 value。由于 key 不能重复，所以，**在`Set`中，没有重复的 key。**\n\n要创建一个`Set`，需要提供一个`Array`作为输入，或者直接创建一个空`Set`：\n\n```js\nvar s1 = new Set() // 空Set\nvar s2 = new Set([1, 2, 3]) // 含1, 2, 3\n```\n\n重复元素在`Set`中自动被过滤：\n\n```js\nvar s = new Set([1, 2, 3, 3, \'3\'])\ns // Set {1, 2, 3, \"3\"}\n```\n\n注意数字`3`和字符串`\'3\'`是不同的元素。\n\n通过`add(key)`方法可以添加元素到`Set`中，可以重复添加，但不会有效果：\n\n```js\ns.add(4)\ns // Set {1, 2, 3, 4}\ns.add(4)\ns // 仍然是 Set {1, 2, 3, 4}\n```\n\n通过`delete(key)`方法可以删除元素：\n\n```js\nvar s = new Set([1, 2, 3])\ns // Set {1, 2, 3}\ns.delete(3)\ns // Set {1, 2}\n```', '2019-02-11 12:51:34', '2019-02-11 12:51:34');
INSERT INTO `article` VALUES (37, 'HTTP - 导学', '## 经典五层网络模型\n\n![](https://user-gold-cdn.xitu.io/2018/11/20/16730e533ec09d3a?w=1360&h=947&f=png&s=282764)\n\n在每一台电脑，每个服务器都有这这几个网络模型层级来维护整个网络数据传输过程。\n\n### 一张图了解 TCP/IP 五层网络模型\n\n![](https://user-gold-cdn.xitu.io/2018/11/20/167316845f6dde61?w=1255&h=629&f=jpeg&s=180804)\n\n- **物理层**：将信息编码成电流脉冲或其它信号用于网上传输；（电线、光缆等）\n- **数据链路层**：数据链路层通过物理网络链路供数据传输。可以简单的理解为：规定了 0 和 1 的分包形式，确定了网络数据包的形式。\n- **网络层**：网络层负责在源和终点之间建立连接;（路由器等）\n- **传输层**： 敲重点！\n  > 传输层向用户提供可靠的端对端(`End-to-End`)服务。 常用的（`TCP／IP`）协议 、`UDP` 协议；\n- **应用层**：敲重点！\n  > 为应用软件提供了很多服务，帮我们实现了 `HTTP` 协议，我们只要按照规则去使用 `HTTP` 协议；它构建于 `TCP` 协议之上；屏蔽了网络传输相关细节。\n\n重点在 **应用层** 和 **传输层** 上：`http` 是在应用层上去实现的，而 `http` 协议基于传输层的 `TCP` `UDP` 协议。\n\n<!--more-->\n\n## HTTP 发展历史\n\n### HTTP/0.9\n\n> 1. `HTTP/0.9` 只支持一种方法—— `Get`，请求只有一行\n> 2. 没有 `header` 等描述数据的信息\n> 3. 服务器发送完毕，就关闭 `TCP` 连接\n\n### HTTP/1.0\n\n> 1. 请求与响应支持 `header`，增加了`状态码`，响应对象的一开始是一个响应状态行\n> 2. 协议版本信息需要随着请求一起发送，支持 `HEAD`，`POST` 方法\n\n### HTTP/1.1\n\n在 `HTTP/1.0` 上增加了一些功能来优化网络链接的过程：\n\n1. **持久连接**\n   > `HTTP/1.0` 版本里一个 HTTP 请求就要在客户端和服务端之间创建一次 `TCP` 连接，在服务器返回完内容后就关闭了。相对来说消耗比较高。\n2. **pipeline**\n   > 我们可以在同个连接里发送多个请求，但是服务端要对这些请求都是要按照顺序进行内容的返回。\n   > 前一个请求等待时间较长，后一个请求处理较快，后一个请求也不能进行内容响应，需要等前一个请求完成后才可响应下次请求，这也是**串行/并行**的概念，而这个在 `HTTP/2.0` 中做了优化\n3. **host 和其他一些命令**\n   > 有了 `host` 之后可以在同一台服务器（物理服务器）同时跑多个不同的 web 服务 ，比如说 `node.js` 的服务、`java` 的服务。\n4. **引入更多缓存控制机制**：如 `etag`，`cache-control`\n5. ...\n\n### HTTP/2.0\n\n1. 使用二进制分帧层\n   > 在 `HTTP/1.1` 中大部分的数据传输都是以字符串方式进行的，`HTTP/2.0` 则在应用层与传输层之间增加一个二进制分帧层。\n   > 同样因为这个好处，`pipeline` 在同个连接里发送多个请求不再需要按照顺序来返回处理。\n2. 头部压缩\n   > 头信息压缩：在 `HTTP/1.1` 里面，我们每次发送和返回请求 `http header` 都是必须要进行完整的发送和返回的，占用带宽。\n   > 使用首部表来跟踪和存储之前发送的键值对，对于相同的内容，不会再每次请求和响应时发送。\n3. 服务端推送\n   > 在 `HTTP/2.0` 中，服务器可以向客户发送请求之外的内容，比如正在请求一个页面时，服务器会把页面相关的 `logo`，`CSS` 等文件直接推送到客户端，而不会等到请求来的时候再发送，因为服务器认为客户端会用到这些东西。这相当于在一个 `HTML` 文档内集合了所有的资源。\n4. ...\n\n## HTTP 的三次握手\n\n[http-tcp 的三次握手四次挥手](https://gershonv.github.io/2018/11/20/http-TCP/)\n\n## URI-URL 和 URN\n\n![](http://ww4.sinaimg.cn/mw690/6941baebgw1evu0o8swewj20go0avq3e.jpg)\n\n- `URI` : `Uniform Resource Identifier`/统一资源标志符\n  - `URL` 和 `URN` 都是 `URI` 的子集\n    > 统一资源标识符（`URI`）提供了一个简单、可扩展的资源标识方式。\n- `URL` : `Uniform Resource Locator`/统一资源定位器\n  - URL 是 Internet 上用来描述信息资源的字符串，主要用在各种 WWW 客户程序和服务器程序上。\n  - 采用 URL 可以用一种统一的格式来描述各种信息资源，包括文件、服务器的地址和目录等。\n  ```js\n  ;`http://user:pass@host.com:80/path?query=string#hash` // @example url 的组成\n  /**\n   * http:// ===> 协议，类似的还有 ftp、https 等\n   * user:pass@host.com:80 ===> 存有该资源的主机IP地址（有时也包括端口号）\n   * /path ===> 主机资源的具体地址。如目录和文件名等。\n   */\n  ```\n- `URN` : 永久统一资源定位符\n  - 在资源移动之后还能被找到\n\n## HTTP 报文\n\n### 请求行\n\n> 声明 请求方法 、主机域名、资源路径 & 协议版本\n\n请求行的组成 = 请求方法 + 请求路径 + 协议版本\n\n```js\nGET /test/hi-there.txt HTTP/1.0\n// 请求行的组成 = 请求方法 + 请求路径 + 协议版本\n```\n\n![](https://user-gold-cdn.xitu.io/2018/9/10/165c0f27ea8bff3b?imageslim)\n\n### 请求头\n\n> 声明 客户端、服务器 / 报文的部分信息\n\n1. 请求和响应报文的通用 Header\n   ![](https://user-gold-cdn.xitu.io/2018/9/10/165c0f27eb051d58?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n2. 常见请求 Header\n   ![](https://user-gold-cdn.xitu.io/2018/9/10/165c0f27ebf1b79f?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n### 请求体\n\n> 存放 需发送给服务器的数据信息\n\n![](https://user-gold-cdn.xitu.io/2018/9/10/165c0f28437eb63d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n除此外还有响应报文，略\n\n## 创建一个简单的 http 服务\n\n```js\nconst http = require(\'http\')\n\nhttp\n  .createServer(function(request, response) {\n    console.log(\'request come\', request.url)\n    response.end(\'123\')\n  })\n  .listen(8888)\n```', '2019-02-11 12:52:15', '2019-02-11 12:52:15');
INSERT INTO `article` VALUES (38, 'HTTP - TCP 三次握手四次挥手', '## TCP 的三次握手\n\n> `HTTP` 请求与 `TCP` 链接之间的关系，在客户端向服务端请求和返回的过程中，是需要去创建一个 `TCP connection`，因为 `HTTP` 是不存在链接这样一个概念的，它只有请求和响应这样一个概念，请求和响应都是一个数据包，中间要通过一个传输通道，这个传输通道就是在 `TCP` 里面创建了一个从客户端发起和服务端接收的一个链接，`TCP` 链接在创建的时候是有一个三次握手(三次网络传输)这样一个消耗在的。\n\n下面是 `TCP` 报文格式图：\n\n![](https://user-gold-cdn.xitu.io/2018/11/21/1673405dad1eced0?w=500&h=380&f=jpeg&s=32339)\n↓↓↓↓\n![](https://user-gold-cdn.xitu.io/2018/11/21/1673405e30495223?w=1046&h=402&f=png&s=27889)\n\n有几个字段需要重点介绍下：\n\n1. Seq 序号: `sequeence number`，用来标识从 TCP 源端向目的端发送的字节流，发起方发送数据时对此进行标记。\n2. 确认序号：`acknowledgment number`，只有 ACK 标志位为 1 时，确认序号字段才有效，`Ack=Seq+1`。\n3. 标志位：共 6 个，即 URG、ACK、PSH、RST、SYN、FIN 等，具体含义如下：\n   - `URG`：紧急指针（urgent pointer）有效。\n   - `ACK`：确认序号有效。\n   - `PSH`：接收方应该尽快将这个报文交给应用层。\n   - `RST`：重置连接。\n   - `SYN`：发起一个新连接。\n   - `FIN`：释放一个连接。\n\n<!--more-->\n\n### 第一次握手\n\n> 客户端发送一个 `TCP` 的 `SYN` 标志位置 1 的包指明客户打算连接的服务器的端口，以及初始序号 X,保存在包头的序列号(`Sequence Number`)字段里。\n\n- **简单记忆： 建立连接，等待服务器确认**\n\n![](https://user-gold-cdn.xitu.io/2017/11/9/6b568a608edadc13bc9be7721b00e48a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n- `Sequeence Number` = X => 标识字节流字段为 X\n- `SYN` = 1 => 发起一个新连接，序号为 1\n\n### 第二次握手\n\n> 服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为 1 同时，将确认序号(Acknowledgement Number)设置为客户的 I S N 加 1 以.即 X+1。\n\n- **简单记忆： 服务器收到请求后确认联机**\n\n![](https://user-gold-cdn.xitu.io/2017/11/9/02f7809aa0b7b0b5db477e180f408535?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n- `Sequeence Number` = Y => 标识字节流字段为 Y\n- `acknowledgment number` = X + 1 => 确认序号为 X + 1\n- `SYN` = 1 => 发起一个新连接，序号为 1\n- `ACK` = 1 => 确认序号有效\n\n### 第三次握手\n\n> 客户端再次发送确认包(ACK)SYN 标志位为 0,ACK 标志位为 1.并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方.并且在数据段放写 ISN 的+1\n\n- **简单记忆：检查 ACK 是否正确, 若正确则建立连接。**\n\n![](https://user-gold-cdn.xitu.io/2017/11/9/c052d0061d70d359e1ac0b5fc48d844a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n- `acknowledgment number` = Y + 1 => 确认序号为 Y + 1\n- `ACK` = 1 => 确认序号有效\n\n## TCP 四次挥手\n\n`TCP`的连接的拆除需要发送四个包，因此称为四次挥手(`four-way handshake`)。客户端或服务器均可主动发起挥手动作，在 socket 编程中，任何一方执行 close()操作即可产生挥手操作。\n\n![](https://user-gold-cdn.xitu.io/2017/11/9/8c7874fafe233c9278509e40e906055c?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n### 为什么建立连接是三次握手，而关闭连接却是四次挥手呢？\n\n这是因为服务端在 `LISTEN` 状态下，收到建立连接请求的 `SYN` 报文后，把 `ACK` 和 `SYN` 放在一个报文里发送给客户端。\n而关闭连接时，当收到对方的 `FIN` 报文时，仅仅表示对方不再发送数据了但是还能接收数据，己方也未必全部数据都发送给对方了，所以己方可以立即 `close`，也可以发送一些数据给对方后，再发送 `FIN` 报文给对方来表示同意现在关闭连接，因此，己方 `ACK` 和 `FIN` 一般都会分开发送。', '2019-02-11 12:53:20', '2019-02-11 12:53:20');
INSERT INTO `article` VALUES (39, 'HTTP - 跨域', '## 什么是跨域\n\n> 跨域，是指浏览器不能执行其他网站的脚本。它是由**浏览器的同源策略**造成的，是浏览器对 JavaScript 实施的安全限制。\n\n我们可以简单的重现浏览器的跨域问题：\n\n- `server.js` 模拟客户端：\n\n```js\nconst http = require(\'http\')\nconst fs = require(\'fs\')\n\nhttp\n  .createServer(function(request, response) {\n    console.log(\'request come\', request.url)\n    const html = fs.readFileSync(\'demo.html\', \'utf8\')\n    response.writeHead(200, {\n      \'Content-Type\': \'text/html\'\n    })\n    response.end(html)\n  })\n  .listen(3300) // http://127.0.0.1:3300\n```\n\n- `demo.html` 展示的页面：\n\n```html\n<body>\n  <div>demo.html</div>\n  <script>\n    const xhr = new XMLHttpRequest()\n    xhr.open(\'GET\', \'http://127.0.0.1:6060\')\n    xhr.send()\n  </script>\n</body>\n```\n\n- `server2.js` 模拟服务端：\n\n```js\nconst http = require(\'http\')\n\nhttp\n  .createServer(function(request, response) {\n    console.log(\'request come\', request.url)\n    response.end(\'server2 response\')\n  })\n  .listen(6060)\n\nconsole.log(\'server listening on 6060\')\n```\n\n打开 `http://127.0.0.1:3300` 即可看到\n\nAccess to XMLHttpRequest at \'`http://127.0.0.1:6060/`\' from origin \'`http://127.0.0.1:3300`\' has been blocked by CORS policy: No \'Access-Control-Allow-Origin\' header is present on the requested resource.\n\n后续将讲到如何去解决这个问题。\n\n<!--more-->\n\n## 常见的跨域场景\n\n> 所谓的同源是指，域名、协议、端口均为相同。\n\n```\nURL                                      说明                    是否允许通信\nhttp://www.domain.com/a.js\nhttp://www.domain.com/b.js         同一域名，不同文件或路径           允许\nhttp://www.domain.com/lab/c.js\n\nhttp://www.domain.com:8000/a.js\nhttp://www.domain.com/b.js         同一域名，不同端口                不允许\n\nhttp://www.domain.com/a.js\nhttps://www.domain.com/b.js        同一域名，不同协议                不允许\n\nhttp://www.domain.com/a.js\nhttp://192.168.4.12/b.js           域名和域名对应相同ip              不允许\n\nhttp://www.domain.com/a.js\nhttp://x.domain.com/b.js           主域相同，子域不同                不允许\nhttp://domain.com/c.js\n\nhttp://www.domain1.com/a.js\nhttp://www.domain2.com/b.js        不同域名                         不允许\n```\n\n跨域的解决方法如下\n\n## JSONP\n\n> `HTML` 标签里，一些标签比如 `script、img` 这样的获取资源的标签是没有跨域限制的\n\n`jsonp` 原生的实现方式（以前面的代码为例）\n\n- `demo.html`\n\n```html\n<body>\n  <div>demo.html</div>\n  <script>\n    // 1. 动态创建 script，并引入地址；2. 插入html中；3.通过callback 回调得到数据\n    let script = document.createElement(\'script\')\n    script.src = \'http://127.0.0.1:6060/login?username=guodada&callback=onBack\'\n    document.body.appendChild(script)\n    function onBack(res) {\n      console.log(res)\n    }\n  </script>\n</body>\n```\n\n- `server2.js` 服务端：\n\n```js\nconst http = require(\'http\')\nconst url = require(\'url\')\n\nhttp\n  .createServer(function(request, response) {\n    console.log(\'request come\', request.url)\n    const data = { name: \'guodada\' } // 需要传递的数据\n\n    const { callback } = url.parse(request.url, true).query // 处理 get 请求, 拿到callback\n\n    response.writeHead(200, { \'Content-Type\': \'application/json;charset=utf-8\' })\n    const jsonpCallback = callback + `(${JSON.stringify(data)})` // 相当于 onBack({\"name\":\"guodada\"})\n    response.end(jsonpCallback)\n  })\n  .listen(6060)\n\nconsole.log(\'server listening on 6060\')\n```\n\n虽然这种方式非常好用，但是一个最大的缺陷是，只能够实现 `get` 请求\n\n## CORS\n\n### 简介\n\n因为是目前主流的跨域解决方案。`CORS` 是一个 W3C 标准，全称是\"跨域资源共享\"（`Cross-origin resource sharing`）。它允许浏览器向跨源服务器，发出 `XMLHttpRequest` 请求，从而克服了 `AJAX` 只能同源使用的限制。\n\n`CORS` 需要浏览器和服务器同时支持。目前，所有浏览器都支持该功能，IE 浏览器不能低于 `IE10`。IE8+：IE8/9 需要使用 `XDomainRequest` 对象来支持 `CORS`。\n\n整个 `CORS` 通信过程，都是浏览器自动完成，不需要用户参与。对于开发者来说，`CORS` 通信与同源的 `AJAX` 通信没有差别，代码完全一样。浏览器一旦发现 `AJAX` 请求跨源，就会自动添加一些附加的头信息，有时还会多出一次附加的请求，但用户不会有感觉。因此，实现 `CORS` 通信的关键是服务器。只要服务器实现了 `CORS` 接口，就可以跨源通信。\n\n浏览器将 `CORS` 请求分成两类：简单请求（`simple request`）和非简单请求（`not-so-simple request`）。\n只要同时满足以下两大条件，就属于简单请求。\n\n1. 请求方式为 `HEAD`、`POST` 或者 `GET`\n2. HTTP 的头信息不超出以下几种字段：\n   - `Accept`\n   - `Accept-Language`\n   - `Content-Language`\n   - `Last-Event-ID`\n   - Content-Type：只限于三个值 `application/x-www-form-urlencoded`、`multipart/form-data`、`text/plain`\n\n### 简单请求\n\n> 对于简单请求，浏览器直接发出 `CORS` 请求。具体来说，就是在头信息之中，增加一个 `Origin` 字段。 下面是一个例子，浏览器发现这次跨源 `AJAX` 请求是简单请求，就自动在头信息之中，添加一个 `Origin` 字段。\n\n`server2.js`\n\n```js\nconst http = require(\'http\')\n\nhttp\n  .createServer(function(request, response) {\n    console.log(\'request come\', request.url)\n\n    response.writeHead(200, {\n      \'Access-Control-Allow-Origin\': \'http://127.0.0.1:3300\', // 只有 http://127.0.0.1:3300 才能访问\n      \'Access-Control-Allow-Credentials\': true, // 允许携带 cookie\n      \'Content-Type\': \'text/html; charset=utf-8\'\n    })\n\n    response.end(\'hello cors\')\n  })\n  .listen(6060)\n\nconsole.log(\'server listening on 6060\')\n```\n\n- `demo.html`\n\n```html\n<body>\n  <div>demo.html</div>\n  <script>\n    const xhr = new XMLHttpRequest()\n    xhr.withCredentials = true // server: \'Access-Control-Allow-Credentials\': true\n\n    xhr.open(\'GET\', \'http://127.0.0.1:6060\')\n    xhr.setRequestHeader(\'Content-Type\', \'application/x-www-form-urlencoded\')\n    xhr.send()\n  </script>\n</body>\n```\n\n- `Access-Control-Allow-Origin` : 该字段是必须的。它的值要么是请求时 `Origin` 字段的值，要么是一个`*`，表示接受任意域名的请求\n- `Access-Control-Allow-Credentials`: 表示是否允许发送 `Cookie`\n- `Access-Control-Expose-Headers`: CORS 请求时，`XMLHttpRequest` 对象的 `getResponseHeader()`方法只能拿到 6 个基本字段：`Cache-Control`、`Content-Language`、`Content-Type`、`Expires`、`Last-Modified`、`Pragma`。如果想拿到其他字段，就必须在 `Access-Control-Expose-Headers` 里面指定。\n\n#### withCredentials 属性\n\n上面说到，CORS 请求默认不发送 `Cookie` 和 `HTTP` 认证信息。如果要把 `Cookie` 发到服务器，一方面要服务器同意，指定 `Access-Control-Allow-Credentials` 字段。\n\n否则，即使服务器同意发送 `Cookie`，浏览器也不会发送。或者，服务器要求设置 `Cookie`，浏览器也不会处理。 但是，如果省略 `withCredentials` 设置，有的浏览器还是会一起发送 `Cookie`。这时，可以显式关闭 `withCredentials`。\n\n需要注意的是，如果要发送 `Cookie`，`Access-Control-Allow-Origin` 就不能设为星号，必须指定明确的、与请求网页一致的域名。同时，`Cookie` 依然遵循同源政策，只有用服务器域名设置的 `Cookie` 才会上传，其他域名的 `Cookie` 并不会上传，且（跨源）原网页代码中的 `document.cookie` 也无法读取服务器域名下的 `Cookie`。\n\n### 非简单请求\n\n> 非简单请求是那种对服务器有特殊要求的请求，比如请求方法是 `PUT` 或 `DELETE`，或者 `Content-Type` 字段的类型是 `application/json`。\n\n非简单请求的 `CORS` 请求，会在正式通信之前，增加一次 `HTTP` 查询请求，称为\"预检\"请求（`preflight`）。\n\n浏览器先询问服务器，当前网页所在的域名是否在服务器的许可名单之中，以及可以使用哪些 `HTTP` 动词和头信息字段。只有得到肯定答复，浏览器才会发出正式的 `XMLHttpRequest` 请求，否则就报错。\n\n- `demo.html`\n\n```js\n// 部分代码\nconst xhr = new XMLHttpRequest()\nxhr.withCredentials = true // 允许携带 cookie\n\nxhr.open(\'PUT\', \'http://127.0.0.1:6060\') // 使用 put 请求，server：\'Access-Control-Request-Method\': \'PUT\'\nxhr.setRequestHeader(\'X-Test-Cors\', \'123\') // 设置预检头\nxhr.send()\n```\n\n- `server2.js`\n\n```js\nconst http = require(\'http\')\n\nhttp\n  .createServer(function(request, response) {\n    response.writeHead(200, {\n      \'Access-Control-Allow-Origin\': \'http://127.0.0.1:3300\', // 只有 http://127.0.0.1:3300 才能访问\n      \'Access-Control-Allow-Credentials\': true, // 允许携带 cookie\n      \'Access-Control-Allow-Headers\': \'X-Test-Cors\', // 预检\n      \'Access-Control-Allow-Methods\': \'POST, PUT, DELETE\', // 支持\n      \'Access-Control-Max-Age\': \'1000\' // 指定本次预检请求的有效期，单位为秒\n    })\n\n    response.end(\'hello cors\')\n  })\n  .listen(6060)\n\nconsole.log(\'server listening on 6060\')\n```\n\n- `Access-Control-Allow-Methods`: 返回的是所有支持的方法，而不单是浏览器请求的那个方法。这是为了避免多次\"预检\"请求。\n- `Access-Control-Allow-Headers`: 如果浏览器请求包括 `Access-Control-Request-Headers` 字段，则 `Access-Control-Allow-Headers` 字段是必需的。\n- `Access-Control-Max-Age`: 用来指定本次预检请求的有效期，单位为秒。\n\n`CORS` 与 `JSONP` 的使用目的相同，但是比 `JSONP` 更强大。`JSONP` 只支持 `GET` 请求，`CORS` 支持所有类型的 `HTTP` 请求。`JSONP` 的优势在于支持老式浏览器，以及可以向不支持 `CORS` 的网站请求数据。\n\n## postMessage\n\n.... 略\n\n## window.name + iframe\n\n.... 略\n\n\n## 参考\n\n- [正确面对跨域，别慌](https://juejin.im/post/5a2f92c65188253e2470f16d#heading-5)', '2019-02-11 12:53:54', '2019-02-11 12:53:54');
INSERT INTO `article` VALUES (40, 'HTTP - 缓存机制', '## 缓存实现的步骤\n\n- 首先是当用户请求资源时，会判断是否有缓存，如果没有，则会向原服务器请求资源。\n- 如果有缓存，则会进入强缓存的范畴，判断缓存是否新鲜\n  - 如果缓存新鲜，则会直接返回缓存副本给客户端。\n  - 如果缓存不新鲜了，则表示强缓存失败，将会进入到**协商缓存**。\n- 协商缓存将判断是否存在 `Etag` 和 `Last-Modified` 首部\n  - 如果未发生变化，则表示命中了协商缓存，会重定向到缓存副本，将资源返回给客户端\n  - 否则的话表示协商缓存未命中，服务器会返回新的资源。\n\n![](https://user-gold-cdn.xitu.io/2018/11/4/166de9f3ae4b1f20?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n## 强缓存\n\n> 服务端告知客户端缓存时间后，由客户端判断并决定是否使用缓存。\n\n强缓存是通过 `Expires` 首部或 `Cache-Control: max-age` 来实现的。\n\n- `Expires`: 响应头，代表该资源的过期时间。\n- `Cache-Control`: 请求/响应头，缓存控制字段，精确控制缓存策略。\n\n<!--more-->\n\n`server.js` - demo\n\n```js\nconst http = require(\'http\')\n\nhttp\n  .createServer(function(request, response) {\n    response.writeHead(200, {\n      \'Content-Type\': \'text/javascript\',\n      \'Conche-Control\': \'max-age=2000, public\' // 缓存时间 2000s；public: 资源允许被中间服务器缓存\n    })\n    response.end(\"console.log(\'script loaded\')\")\n  })\n  .listen(3301)\n\nconsole.log(\'http://127.0.0.1:3301\')\n```\n\n### Expires(HTTP/1.0)\n\n`Expires` 和 `Cache-Control: max-age` 都是用来标识资源的过期时间的首部。\n\n由于 `expires` 是一个绝对时间，如果人为的更改时间，会对缓存的有效期造成影响，使缓存有效期的设置失去意义。因此在 `http1.1` 中我们有了 `expires` 的完全替代首部 `cache-control：max-age`\n\n### Cache-Control(HTTP/1.1)\n\n除了可以设置 `max-age` 相对过期时间以外，还可以设置成如下几种值：\n\n- `public`，资源允许被中间服务器缓存。\n\n  > 浏览器请求服务器时，如果缓存时间没到，中间服务器直接返回给浏览器内容，而不必请求源服务器。\n\n- `private`，资源不允许被中间代理服务器缓存\n\n  > 浏览器请求服务器时，中间服务器都要把浏览器的请求透传给服务器。\n\n- `no-cache`，浏览器不做缓存检查。\n\n  > 每次访问资源，浏览器都要向服务器询问，如果文件没变化，服务器只告诉浏览器继续使用缓存（304）。\n\n- `no-store`，浏览器和中间代理服务器都不能缓存资源。\n  > 每次访问资源，浏览器都必须请求服务器，并且，服务器不去检查文件是否变化，而是直接返回完整的资源。\n- `must-revalidate`，可以缓存，但是使用之前必须先向源服务器确认。\n- `proxy-revalidate`，要求缓存服务器针对缓存资源向源服务器进行确认。\n- `s-maxage`：缓存服务器对资源缓存的最大时间。\n\n`Cache-Control` 对缓存的控制粒度更细，包括缓存代理服务器的缓存控制。\n\n## 协商缓存\n\n> 由服务端决定并告知客户端是否使用缓存。\n\n协商缓存机制下，浏览器需要向服务器去询问缓存的相关信息，进而判断是重新发起请求、下载完整的响应，还是从本地获取缓存的资源。\n协商缓存是通过请求头 `Last-Modified` 或 `Etag` 来实现的。\n\n- `Last-Modified` 标识的是文档最后修改时间\n- `Etag` 则是以文档内容来进行编码的。\n\n### Last-Modified\n\n> 响应头，资源最近修改时间，由服务器告诉浏览器。\n\n`Last-Modified` （上次修改时间）主要配合 `If-Modified-Since` 或者 `If-Unmodified-Since` 使用， 对比上次修改时间以验证资源是否需要更新\n\nIf-Modified-Since: 请求头\n\n![](https://user-gold-cdn.xitu.io/2018/11/4/166de151763c87aa?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n- 首次请求资源时，服务器在返回资源的同时，会在 `Response Headers` 中写入 `Last-Modified` 首部，表示该资源在服务器上的最后修改时间。\n- 当再次请求该资源时，会在 `Request Headers` 中写入 `If-Modified-Since` 首部，此时的 `If-Modified-Since` 的值是首次请求资源时所返回的 `Last-Modified` 的值。\n- 服务器接收到请求后，会根据 `If-Modified-Since` 的值判断资源在该日期之后是否发生过变化。\n- 如果没有，则会返回 `304 Not Modified`;如果变化了，则会返回变化过后的资源，同时更新 `Last-Modified` 的值。\n\n1. 资源未更新 network 面板截图\n   ![](https://user-gold-cdn.xitu.io/2018/11/23/1673e75620b00e06?w=848&h=520&f=png&s=224745)\n\n2. 资源发生更新 network 面板截图\n   ![](https://user-gold-cdn.xitu.io/2018/11/23/1673e7991fe685a1?w=935&h=484&f=png&s=245226)\n   可以看到 `Last-Modified` 和 `If-Modified-Since` 标识的时间不一样\n\n- `server.js` - demo\n\n```js\nconst http = require(\'http\')\n\nhttp\n  .createServer(function(request, response) {\n    response.writeHead(200, {\n      \'Content-Type\': \'text/javascript\',\n      \'Conche-Control\': \'max-age=2000, public\', // 缓存时间 2000s；public: 资源允许被中间服务器缓存\n      \'Last-Modified\': \'123\'\n    })\n    response.end(\"console.log(\'script loaded\')\")\n  })\n  .listen(3301)\n\nconsole.log(\'http://127.0.0.1:3301\')\n```\n\n### Etag\n\n> 响应头，资源标识，由服务器告诉浏览器。\n\n`Etag` 和 `If-None-Match` 配合使用， （文件内容对比）对比资源的签名来决定是否使用缓存。\n\n- `server.js` - demo\n\n```js\nconst http = require(\'http\')\n\nhttp\n  .createServer(function(request, response) {\n    const etag = request.headers[\'if-none-match\']\n    if (etag === \'777\') {\n      response.writeHead(304, {\n        \'Content-Type\': \'text/javascript\',\n        \'Cache-Control\': \'max-age=120, no-cache\', // 缓存时间 120s；no-cache: 浏览器不做缓存检查\n        \'Last-Modified\': \'123\',\n        Etag: \'777\'\n      })\n      response.end()\n    } else {\n      // etag change\n      response.writeHead(200, {\n        \'Content-Type\': \'text/javascript\',\n        \'Conche-Control\': \'max-age=120, no-cache\', // 缓存时间 120s；no-cache: 浏览器不做缓存检查\n        \'Last-Modified\': \'123\',\n        Etag: \'777\'\n      })\n      response.end(\"console.log(\'script loaded\')\")\n    }\n  })\n  .listen(3301)\n\nconsole.log(\'http://127.0.0.1:3301\')\n```\n\n## 总结与缓存方案\n\n![](https://user-gold-cdn.xitu.io/2018/8/13/16531214dfa218be?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n1. 服务器和浏览器约定资源过期时间 `Cache-Control: expires=xxx`\n2. 服务器告诉浏览器资源上次修改时间 `Last-Modified`\n3. 增加相对时间的控制 `Cache-Control: max-age=xxx`\n4. 增加文件内容对比，引入`Etag`\n\n缓存优先级\n\n> `Pragma` > `Cache-Control` > `Expires` > `ETag` > `Last-Modified`\n\n参考与相关链接：\n\n- [浅谈 HTTP 缓存](https://juejin.im/post/5bdeabbbe51d4505466cd741#heading-25)\n- [面试精选之 http 缓存](https://juejin.im/post/5b3c87386fb9a04f9a5cb037#heading-0)', '2019-02-11 12:54:29', '2019-02-11 12:54:29');
INSERT INTO `article` VALUES (41, 'HTTP - keep-alive', '## HTTP 协议是无状态的\n\n`HTTP` 协议是无状态的，指的是协议对于事务处理没有记忆能力，服务器不知道客户端是什么状态。也就是说，打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。`HTTP` 是一个无状态的面向连接的协议，无状态不代表 `HTTP` 不能保持 `TCP` 连接，更不能代表 `HTTP` 使用的是 `UDP` 协议（无连接）。\n\n## 什么是长连接、短连接？\n\n在 `HTTP/1.0` 中，默认使用的是短连接。也就是说，浏览器和服务器每进行一次 `HTTP` 操作，就要经过[三次握手](https://gershonv.github.io/2018/11/21/http-TCP/)建立一次连接，但任务结束就中断连接。\n\n客户端浏览器访问的某个 `HTML` 或其他类型的 `Web` 页中包含有其他的 `Web` 资源，如 JavaScript 文件、图像文件、CSS 文件等；当浏览器每遇到这样一个 Web 资源，就会建立一个 HTTP 会话。但从  `HTTP/1.1` 起，默认使用长连接，用以保持连接特性。使用长连接的 HTTP 协议，会在响应头有加入这行代码：\n\n```js\n\'Connection\': \'keep-alive\'\n```\n<!--more-->\n\n## 实战\n\n- `test.html`\n\n```html\n<body>\n  <img src=\"/test1.jpg\" alt=\"\" /> <img src=\"/test2.jpg\" alt=\"\" />\n  <img src=\"/test3.jpg\" alt=\"\" /> <img src=\"/test4.jpg\" alt=\"\" />\n  <img src=\"/test5.jpg\" alt=\"\" /> <img src=\"/test6.jpg\" alt=\"\" />\n  <img src=\"/test7.jpg\" alt=\"\" /> <img src=\"/test11.jpg\" alt=\"\" />\n  <img src=\"/test12.jpg\" alt=\"\" /> <img src=\"/test13.jpg\" alt=\"\" />\n  <img src=\"/test14.jpg\" alt=\"\" /> <img src=\"/test15.jpg\" alt=\"\" />\n  <img src=\"/test16.jpg\" alt=\"\" /> <img src=\"/test17.jpg\" alt=\"\" />\n  <img src=\"/test111.jpg\" alt=\"\" /> <img src=\"/test112.jpg\" alt=\"\" />\n  <img src=\"/test113.jpg\" alt=\"\" /> <img src=\"/test114.jpg\" alt=\"\" />\n  <img src=\"/test115.jpg\" alt=\"\" /> <img src=\"/test116.jpg\" alt=\"\" />\n</body>\n```\n\n- `server.js`\n\n```js\nconst http = require(\'http\')\nconst fs = require(\'fs\')\n\nhttp\n  .createServer(function(request, response) {\n    console.log(\'request come\', request.url)\n\n    const html = fs.readFileSync(\'test.html\', \'utf8\')\n    const img = fs.readFileSync(\'test.jpg\')\n    if (request.url === \'/\') {\n      response.writeHead(200, {\n        \'Content-Type\': \'text/html\'\n      })\n      response.end(html)\n    } else {\n      response.writeHead(200, {\n        \'Content-Type\': \'image/jpg\',\n        Connection: \'keep-alive\' // or close HTTP/1.1 起 默认 keep-alive\n      })\n      response.end(img)\n    }\n  })\n  .listen(8888)\n\nconsole.log(\'server listening on 8888\')\n```', '2019-02-11 12:55:47', '2019-02-11 12:55:47');
INSERT INTO `article` VALUES (42, 'HTTP - 浏览器输入 url 后 http 请求返回的完整过程', '## 示意图\n\n![](https://user-gold-cdn.xitu.io/2018/11/20/167306e21f25ced5?w=1234&h=443&f=png&s=181984)\n\n## 检查缓存\n\n> 缓存就是把你之前访问的 web 资源，比如一些 js，css，图片什么的保存在你本机的内存或者磁盘当中。\n\n浏览器获取了这个 `url`，当然就去解析了，它先去缓存当中看看有没有，从 浏览器缓存-系统缓存-路由器缓存 当中查看，\n如果有从缓存当中显示页面， 如果没有缓存则进行 `DNS` 解析\n\n浏览器缓存相关链接：[HTTP - 缓存机制](https://gershonv.github.io/2018/11/23/http-cache/)\n\n这里重点介绍 浏览器中 [HTTP - 缓存机制](https://gershonv.github.io/2018/11/23/http-cache/)， 因为个人对系统缓存以及路由器缓存认识较少\n\n![](https://user-gold-cdn.xitu.io/2018/8/13/16531214dfa218be?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n- 缓存是否到时: `Cache-Control: max-age=xxx`\n- 缓存是否过期：`Expires` （如果设置）\n- 资源是否发生修改: `ETag`\n  - `If-None-Match` => 304 未修改\n- 文件的修改时间: `Last-Modified`\n  - `If-Modified-Since` => 304 未修改\n\n<!--more-->\n\n## DNS 解析\n\n> 在发送 `http` 之前，需要进行 `DNS` 解析即域名解析。\n> `DNS` 解析:域名到 `IP` 地址的转换过程。域名的解析工作由 `DNS` 服务器完成。解析后可以获取域名相应的 `IP` 地址\n\n根据 URL 找到对应的 IP 地址。这一步通常被称为 DNS 轮询，这里面是有缓存机制的。缓存的顺序依次为：浏览器缓存->操作系统缓存->路由器缓存->DNS 提供商缓存->DNS 提供商轮询。\n\n## 创建 TCP 链接\n\n[TCP 三次握手四次挥手](https://gershonv.github.io/2018/11/21/http-TCP/)\n\n- 第一次握手： `client` => `server`\n  - `SYN = 1` （SYN 代表发起一个新连接）； `Sequence Number` = 1 （请求的标记）\n- 第二次握手：`server` => `client`\n  - `SYN = 1` （SYN 代表发起一个新连接）；`Sequence Number` = Y （请求的标记）\n  - `acknowledgment number` = 1 （确认序号，只有 `ACK` 标志位为 1 时，确认序号字段才有效）\n  - `ACK` = 1 确认序号字段有效\n- 第三次握手：`client` => `server`\n  - `acknowledgment number` = Y + 1 => 确认序号为 Y + 1\n  - `ACK` = 1 确认序号字段有效\n\n这样 TCP 连接就建立了。\n在此之后，浏览器开始向服务器发送 `http` 请求，请求数据包。请求信息包含一个头部和一个请求体。\n\n## 发送请求\n\n相关链接：[HTTP - 导学](https://gershonv.github.io/2018/11/20/http-导学/)\n\n## 响应请求\n\n浏览器对于每一种请求类型的处理方式是不一样的，像 `text/html`、`application/JavaScript`、`text/plain` 等等这些是可以直接呈现的，而对于不能呈现的类型，浏览器会将该资源下载到本地。\n\n那么浏览器在确认这个 response 的状态不是 301（跳转）或者 401（未授权）或其它需要做特殊处理的状态，之后开始进入呈现过程。\n\n`Renderer` 进程开始解析 `css rule tree` 和 `dom tree`，这两个过程是并行的，所以一般我会把 link 标签放在页面顶部。\n\n解析绘制过程中，当浏览器遇到 `link` 标签或者 ` script``、img ` 等标签，浏览器会去下载这些内容，遇到时候缓存的使用缓存，不适用缓存的重新下载资源。\n\n`css rule tree` 和 `dom tree` 生成完了之后，开始合成 `render tree`，这个时候浏览器会进行 `layout`，开始计算每一个节点的位置，然后进行绘制。\n\n绘制结束后，关闭 `TCP` 连接，过程有四次挥手。', '2019-02-11 12:56:10', '2019-02-11 12:56:10');
INSERT INTO `article` VALUES (43, '正则表达式', '## 匹配出现次数\n\n> 横向模糊指的是，一个正则可匹配的字符串的长度不是固定的，可以是多种情况的。\n\n`{m,n}`，表示连续出现最少 m 次，最多 n 次。\n\n```js\nvar regx = /ab{2,5}c/\nvar string = \'abc abbc abbbc abbbbc abbbbbc abbbbbbc\'\nstring.match(regex) // => [\"abbc\", \"abbbc\", \"abbbbc\", \"abbbbbc\"]\n```\n\n## 匹配范围\n\n> 纵向模糊指的是，一个正则匹配的字符串，具体到某一位字符时，它可以不是某个确定的字符，可以有多种\n> 可能。\n\n```js\nvar regex = /a[123]b/g\nvar string = \'a0b a1b a2b a3b a4b\'\nconsole.log(string.match(regex)) // => [\"a1b\", \"a2b\", \"a3b\"]\n```\n\n- `[123]` : 匹配 123 中任意一位数\n\n* `[^123]` : 匹配除 1 2 3 之外的任意一个字符\n\n  ```js\n  var regx = /[^123]/\n  regx.test(\'12\') // false\n  regx.test(\'1234\') // true\n  ```\n\n* `[a-zA-Z]` : 匹配 26 位字母\n* `[0-9]` : 匹配数字范围\n* `/.^/` : 不匹配任何东西\n\n<!--more-->\n\n### 贪婪匹配与惰性匹配\n\n```js\nvar regex = /\\d{2,5}/g\nvar string = \'123 1234 12345 123456\'\nconsole.log(string.match(regex)) // => [\"123\", \"1234\", \"12345\", \"12345\"]\n```\n\n其中正则 `/\\d{2,5}/`，表示数字连续出现 2 到 5 次。会匹配 2 位、3 位、4 位、5 位连续数字。\n\n但是其是贪婪的，它会尽可能多的匹配。你能给我 6 个，我就要 5 个。你能给我 3 个，我就要 3 个。\n反正只要在能力范围内，越多越好。\n\n我们知道有时贪婪不是一件好事, 而惰性匹配，就是尽可能少的匹配：\n\n```js\nvar regex = /\\d{2,5}?/g\nvar string = \'123 1234 12345 123456\'\nconsole.log(string.match(regex)) // => [\"12\", \"12\", \"34\", \"12\", \"34\", \"12\", \"34\", \"56\"]\n```\n\n其中 `/\\d{2,5}?/` 表示，虽然 2 到 5 次都行，当 2 个就够的时候，就不再往下尝试了。\n\n通过在量词后面加个问号就能实现惰性匹配，因此所有惰性匹配情形如下：\n\n| 惰性量词 | 贪婪量词 |\n| -------- | -------- |\n| {m,n}?   | {m,n}    |\n| {m,}?    | {m,}     |\n| ??       | ?        |\n| +?       | +        |\n| \\_?      | \\_       |\n\n对惰性匹配的记忆方式是：量词后面加个问号，问一问你知足了吗，你很贪婪吗？\n\n## 匹配多项\n\n> 一个模式可以实现横向和纵向模糊匹配。而多选分支可以支持多个子模式任选其一。\n\n具体形式如下：`(p1|p2|p3)`，其中`p1`、`p2`和`p3`是子模式，用`|`（管道符）分隔，表示其中任何之一。\n\n例如要匹配\"good\"和\"nice\"可以使用`/good|nice/`。测试如下：\n\n```js\nvar regex = /good|nice/g\nvar string = \'good idea, nice try.\'\nconsole.log(string.match(regex)) // => [\"good\", \"nice\"]\n```\n\n但有个事实我们应该注意，比如我用/`good|goodbye/`，去匹配\"goodbye\"字符串时，结果是\"good\"：\n\n```js\nvar regex = /good|goodbye/g\nvar string = \"goodbye\"\nconsole.log( string.match(regex) ) // => [\"good\"]\n\n// 而把正则改成/goodbye|good/，结果是：\nvar regex2 = /goodbye|good/g\nconsole.log( string.match(regex) ) // => [\"goodbye\"]\n```\n\n也就是说，分支结构也是惰性的，即当前面的匹配上了，后面的就不再尝试了。\n\n## 匹配位置\n\n- `^` : 匹配开头\n- `$` : 匹配结尾\n- `\\b` : 匹配单词边界\n- `\\B` : 匹配非单词边界\n- `(?=p)` : 匹配 p 前面的位置\n- `(?!p)` : 匹配 p 后面的位置\n\n1. `^ $`\n  ```js\n  // 字符串的开头和结尾用\"#\"替换：\n  var result = \"hello\".replace(/^|$/g, \'#\')\n  console.log(result) // => \"#hello#\"\n  ```\n2. `\\b \\B`\n  ```js\n  var result = \"[JS] Lesson_01.mp4\".replace(/\\b/g, \'#\')\n  console.log(result) // => \"[#JS#] #Lesson_01#.#mp4#\"\n  ```\n3. `(?=p)(?!p)`\n   ```js\n   var result = \"hello\".replace(/(?=l)/g, \'#\') // \"he#l#lo\"\n   var result2 = \"hello\".replace(/(?!l)/g, \'#\') // \"#h#ell#o#\"\n   ```\n\n数字的千位分隔符表示法\n\n```js\nvar result = \"12345678\".replace(/(?=\\d{3}$)/g, \',\') // 12,345,678\n```\n\n## 分组\n\n我们知道`/a+/`匹配连续出现的“a”，而要匹配连续出现的“ab”时，需要使用`/(ab)+/`。\n\n其中括号是提供分组功能，使量词`+`作用于“ab”这个整体，测试如下：\n\n```js\nvar regex = /(ab)+/g\nvar string = \'ababa abbb ababab\'\nconsole.log(string.match(regex)) // => [\"abab\", \"ab\", \"ababab\"]\n```\n\n### 引用分组\n\n> 这是括号一个重要的作用，有了它，我们就可以进行数据提取，以及更强大的替换操作。\n\n```js\nvar regex = /(\\d{4})-(\\d{2})-(\\d{2})/\nvar string = \"2017-06-12\"\n\nstring.replace(regex, ($1, $2, $3) => {\n  console.log($1, $2, $3) // 2017-06-12 2017 06\n})\n\nvar result = string.replace(regex, \'$2/$3/$1\') // 06/12/2017\n```\n\n...\n\n## 实战\n\n1. 0 ≤ x ≤ 50 整数: `/^(\\d|[1-4]\\d|50)$/g`\n2. -1000 ≤ x ≤ 1000 : `/^-?(\\d{1,3}|1000)$/`\n3. -1000 ≤ x ≤ 2000 : `/^-?(\\d{1,3}|1000)$|^(1\\d{3}|2000)$/`\n\n\n### 匹配数字范围\n\n\n\n\n## 参考\n\n[JS正则表达式完整教程（略长）](https://juejin.im/post/5965943ff265da6c30653879#heading-35)', '2019-02-11 12:57:05', '2019-02-11 12:57:05');
INSERT INTO `article` VALUES (44, 'js - 继承', '# 继承\n\n- [原型链](#原型链)\n  - `prototype` 共享属性/方法，实例之间相互影响\n  - 执行对给定对象的浅\n    复制 **child.prototype = new Father\\(\\)**\n- [借用构造函数](#借用构造函数)\n  - 解决原型中包含引用类型值所带来问题，通过使用 call、apply 在新创建的对象执行构造函数\n  - **function Child\\(\\){ Father.call\\(this\\) }**\n  - 无法实现函数复用\n- [组合继承](#组合继承)\n  - 使用原型链实现对原型属性和方法的继承，使用构造函数来实现对实例属性的继承。\n  - 实现了函数\n    复用，又能够保证每个实例都有它自己的属性\n  - 组合继承最大的\n    问题就是无论什么情况下，都会调用两次超类型构造函数\n- [寄生组合式继承](#寄生组合式继承)\n  - 使用寄生式继承来继承超类型的原型，然后再将结果指定给子类型\n    的原型\n\n继承是 OO 语言中的一个最为人津津乐道的概念。许多 OO 语言都支持两种继承方式：接口继承和实现继承。接口继承只继承方法签名，而实现继承则继承实际的方法。\n\n<!--more-->\n\n## 原型链\n\nECMAScript 中描述了原型链的概念，并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系：每个构造函数都有一个原型对象，原型对象都包含一个指向构造函数的指针，而实例都包含一个指向原型对象的内部指针。那么，假如我们让原型对象等于另一个类型的实例，结果会怎么样呢？显然，此时的原型对象将包含一个指向另一个原型的指针，相应地，另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例，那么上述关系依然成立，如此层层递进，就构成了实例与原型的链条。这就是所谓原型链的基本概念。\n\n实现原型链有一种基本模式，其代码大致如下。\n\n```js\nfunction SuperType() {\n  this.property = true\n}\n\nSuperType.prototype.getSuperValue = function() {\n  return this.property\n}\n\nfunction SubType() {\n  this.Subproperty = false\n}\n\nSubType.prototype = new SuperType()\n\nSuperType.prototype.getSubValue = function() {\n  return this.subproperty\n}\n\nvar instance = new SubType()\nconsole.log(instance.getSuperValue())\n```\n\n以上代码定义了两个类型： SuperType 和 SubType 。每个类型分别有一个属性和一个方法。它们的主要区别是 SubType 继承了 SuperType ，而继承是通过创建 SuperType 的实例，并将该实例赋给 SubType.prototype 实现的。实现的本质是重写原型对象，代之以一个新类型的实例。换句话说，原来存在于 SuperType 的实例中的所有属性和方法，现在也存在于 SubType.prototype 中了。在确立了继承关系之后，我们给 SubType.prototype 添加了一个方法，这样就在继承了 SuperType 的属性和方法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如图\n\n![](/assets/prototype2.png)\n\n通过原型链，本质上扩展了原型搜索机制。在 instance.getSuperValue\\(\\)调用会经历三个搜索步骤\n\n1. 搜索实例\n2. 搜索原型对象\n3. 搜索父类原型对象\n\ninstance 指向 SubType 的 原 型 ， SubType 的 原 型 又 指 向 SuperType 的 原 型 。 getSuperValue\\(\\) 方 法 仍 然 还 在 SuperType.prototype 中，但 property 则位于 SubType.prototype 中。\n\n#### **原型链的问题**\n\n原型链这么强大，同样也会造成问题。最主要的问题来自于包含引用类型的原型。引用类型的原型属性会被虽有实例共享，在通过原型来实现继承时，原型会变成另一个类型的实例，原先的实例属性也就顺理成章变成来现在的原型属性。\n\n```js\nfunction SuperType() {\n  this.colors = [\'red\', \'blue\', \'green\']\n}\n\nfunction SubType() {}\n//继承了 SuperType\nSubType.prototype = new SuperType()\nvar instance1 = new SubType()\ninstance1.colors.push(\'black\')\nalert(instance1.colors) //\"red,blue,green,black\"\nvar instance2 = new SubType()\nalert(instance2.colors) //\"red,blue,green,black\" ======> 实例 instance2 的 colors 也被更新了\n```\n\n还有一个问题，就是不能在创建子类性时，像父类型的构造函数传递参数。所以我们一般很少单独使用原型链。\n\n## 借用构造函数\n\n在解决原型中包含引用类型值所带来问题的过程中，开发人员开始使用一种叫做借用构造函数（constructor stealing）的技术。这种技术的基本思想相当简单，即在子类型构造函数的内部调用超类型构造函数。别忘了，函数只不过是在特定环境中执行代码的对象，  \n**因此通过使用 apply\\(\\) 和 call\\(\\) 方法也可以在（将来）新创建的对象上执行构造函数**，如下所示：\n\n```js\nfunction SuperType() {\n  this.colors = [\'red\', \'blue\', \'green\']\n}\n\nfunction SubType() {\n  // 继承了 SuperType\n  SuperType.call(this)\n  // 通过使用 call() 方法 在新创建的 SubType 实例的环境下调用了 SuperType 构造函数\n  // 这样一来，就会在新 SubType 对象上执行 SuperType() 函数中定义的所有对象初始化代码\n}\nvar instance1 = new SubType()\ninstance1.colors.push(\'black\')\nalert(instance1.colors) //\"red,blue,green,black\"\nvar instance2 = new SubType()\nalert(instance2.colors) //\"red,blue,green\"\n```\n\n借用构造函数的问题：如果仅仅是借用构造函数，那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义，因此函数复用就无从谈起了。而且，在超类型的原型中定义的方法，对子类型而言也是不可见的，结果所有类型都只能使用构造函数模式。考虑到这些问题，借用构造函数的技术也是很少单独使用的。\n\n## 组合继承\n\n将原型链和借用构造函数的\n技术组合到一块：其背后的思路是使用原型链实现对原型属性和方\n法的继承，而通过借用构造函数来实现对实例属性的继承。这样，既通过在原型上定义方法实现了函数\n复用，又能够保证每个实例都有它自己的属性。\n\n```js\nfunction SuperType(name) { //SuperType 构造函数定义了两个属性： name 和 colors\n    this.name = name;\n    this.colors = [\"red\", \"blue\", \"green\"];\n}\nSuperType.prototype.sayName = function () { // 原型上定义了sayName方法，其实例共享这个方法\n    alert(this.name);\n};\n\nfunction SubType(name, age) {\n    //继承属性\n    SuperType.call(this, name); // 调用 SuperType 构造函数时传入了 name 参数\n    this.age = age;             // 定义了它自己的属性 age\n}\n//继承方法\nSubType.prototype = new SuperType();     // 将 SuperType 的实例赋值给 SubType 的原型\nSubType.prototype.constructor = SubType;\nSubType.prototype.sayAge = function () { // 在该新原型\n上定义了方法 sayAge()\n    alert(this.age);\n};\nvar instance1 = new SubType(\"Nicholas\", 29);\ninstance1.colors.push(\"black\");\nalert(instance1.colors);         //\"red,blue,green,black\"\ninstance1.sayName();             //\"Nicholas\";\ninstance1.sayAge();              //29\nvar instance2 = new SubType(\"Greg\", 27);\nalert(instance2.colors);     //\"red,blue,green\"\ninstance2.sayName();         //\"Greg\";\ninstance2.sayAge();         //27\n```\n\n1. 在这个例子中， SuperType 构造函数定义了两个属性： name 和 colors ，其原型定义了一个方法 sayName\n2. SubType 构造函数在调用 SuperType 构造函数时传入了 name 参数，紧接着\n   又定义了它自己的属性 age 。\n3. 然后，将 SuperType 的实例赋值给 SubType 的原型，然后又在该新原型\n   上定义了方法 sayAge\\(\\)\n4. 这样一来，就可以让两个不同的 SubType 实例既分别拥有自己属性——包\n   括 colors 属性，又可以使用相同的方法了。\n\n组合继承避免了原型链和借用构造函数的缺陷，融合了它们的优点，成为 JavaScript 中最常用的继承模式。而且， instanceof 和 isPrototypeOf\\(\\) 也能够用于识别基于组合继承创建的对象。\n\n## 寄生组合式继承\n\n组合继承是 JavaScript 最常用的继承模式；不过，它也有自己的不足。组合继承最大的\n问题就是无论什么情况下，都会调用两次超类型构造函数：一次是在创建子类型原型的时候，另一次是\n在子类型构造函数内部。没错，子类型最终会包含超类型对象的全部实例属性，但我们不得不在调用子\n类型构造函数时重写这些属性。\n\n```js\nfunction SuperType(name) {\n  this.name = name\n}\n\nSuperType.prototype.sayName = function() {\n  console.log(this.name)\n}\n\nfunction SubType(name, age) {\n  // 继承了 SuperType\n  SuperType.call(this, name) //第二次调用\n\n  this.age = age\n}\nSubType.prototype = new SuperType() // 第一次调用\n\nvar instance1 = new SubType()\nconsole.log(instance1.name)\n```\n\n我们不必为了指定子类型的原型调用超类型的构造函数，我们所需要的不过是超类型原型的一个副本而已。本质上，就是使用寄生式继承来继承超类型的原型，然后再将结果指定给子类型\n的原型。寄生组合式继承的基本模式如下所示。\n\n```js\nfunction inheritPrototype(subType, superType) {\n  var prototype = object(superType.prototype) //创建对象\n  prototype.constructor = subType //增强对象\n  subType.prototype = prototype //指定对象\n}\n```\n\n```js\nfunction SuperType(name) {\n  this.name = name\n  this.color = [\'red\', \'blue\', \'green\']\n}\n\nSuperType.prototype.sayName = function() {\n  console.log(this.name)\n}\n\nfunction SubType(name, age) {\n  SuperType.call(this, name)\n  this.age = age\n}\n\nSubType.prototype = Object.create(SuperType.prototype)\n\nSubType.prototype.constructor = SubType\n```', '2019-02-11 12:57:38', '2019-02-11 12:57:38');
INSERT INTO `article` VALUES (45, 'js - 常用的设计模式', '## 发布订阅模式\n\n发布---订阅模式又叫观察者模式，它定义了对象间的一种一对多的关系，让多个观察者对象同时监听某一个主题对象，当一个对象发生改变时，所有依赖于它的对象都将得到通知。\n\n<!--more-->\n\n```js\nclass Bus {\n  constructor() {\n    this.watchers = {} // 存放事件\n  }\n\n  on(event, handler) {\n    this.watchers[event] = this.watchers[event] || []\n    this.watchers[event].push(handler)\n  }\n\n  emit(event, ...args) {\n    if (this.watchers[event]) {\n      this.watchers[event].forEach(cb => cb(...args))\n    }\n  }\n\n  off(event, handler) {\n    if (event && handler) {\n      if (this.watchers[event] && this.watchers[event].length) {\n        const index = this.watchers[event].findIndex(cb => Object.is(cb, handler)) // 找到需要退订的函数...\n        this.watchers[event].splice(index, 1)\n      }\n    } else if (event) {\n      // 如果仅传入事件名称，则退订此事件对应的所有的事件函数\n      this.watchers[event] = []\n    } else {\n      this.watchers = {}\n    }\n  }\n}\n\nconst bus = new Bus()\n\nconst fn1 = (...args) => {\n  console.log(\'emit function one, params is: \', ...args)\n}\n\nconst fn2 = (...args) => {\n  console.log(\'emit function two, params is: \', ...args)\n}\n\nbus.on(\'a\', fn1)\nbus.on(\'a\', fn2)\nbus.emit(\'a\', 1) // emit function one/two, params is:  1 2\n\nbus.off(\'a\', fn1)\nbus.emit(\'a\', 2) // emit function two, params is:  2\nbus.off(\'a\') // remove all the listeners\n```\n\n## 中介者模式\n\n观察者模式通过维护一堆列表来管理对象间的多对多关系，中介者模式通过统一接口来维护一对多关系，且通信者之间不需要知道彼此之间的关系，只需要约定好 API 即可。\n\n```js\n// 汽车\nclass Bus {\n  constructor() {\n    // 初始化所有乘客\n    this.passengers = {}\n  }\n\n  // 发布广播\n  broadcast(passenger, message = passenger) {\n    // 如果车上有乘客\n    if (Object.keys(this.passengers).length) {\n      // 如果是针对某个乘客发的，就单独给他听\n      if (passenger.id && passenger.listen) {\n        // 乘客他爱听不听\n        if (this.passengers[passenger.id]) {\n          this.passengers[passenger.id].listen(message)\n        }\n\n        // 不然就广播给所有乘客\n      } else {\n        Object.keys(this.passengers).forEach(passenger => {\n          if (this.passengers[passenger].listen) {\n            this.passengers[passenger].listen(message)\n          }\n        })\n      }\n    }\n  }\n\n  // 乘客上车\n  aboard(passenger) {\n    this.passengers[passenger.id] = passenger\n  }\n\n  // 乘客下车\n  debus(passenger) {\n    this.passengers[passenger.id] = null\n    delete this.passengers[passenger.id]\n    console.log(`乘客${passenger.id}下车`)\n  }\n\n  // 开车\n  start() {\n    this.broadcast({ type: 1, content: \'前方无障碍，开车！Over\' })\n  }\n\n  // 停车\n  end() {\n    this.broadcast({ type: 2, content: \'老司机翻车，停车！Over\' })\n  }\n}\n\n// 乘客\nclass Passenger {\n  constructor(id) {\n    this.id = id\n  }\n\n  // 听广播\n  listen(message) {\n    console.log(`乘客${this.id}收到消息`, message)\n    // 乘客发现停车了，于是自己下车\n    if (Object.is(message.type, 2)) {\n      this.debus()\n    }\n  }\n\n  // 下车\n  debus() {\n    console.log(`我是乘客${this.id}，我现在要下车`, bus)\n    bus.debus(this)\n  }\n}\n\n// 创建一辆汽车\nconst bus = new Bus()\n\n// 创建两个乘客\nconst passenger1 = new Passenger(1)\nconst passenger2 = new Passenger(2)\n\n// 俩乘客分别上车\nbus.aboard(passenger1)\nbus.aboard(passenger2)\n\n// 2秒后开车\nsetTimeout(bus.start.bind(bus), 2000)\n\n// 3秒时司机发现2号乘客没买票，2号乘客被驱逐下车\nsetTimeout(() => {\n  bus.broadcast(passenger2, { type: 3, content: \'同志你好，你没买票，请下车!\' })\n  bus.debus(passenger2)\n}, 3000)\n\n// 4秒后到站停车\nsetTimeout(bus.end.bind(bus), 3600)\n\n// 6秒后再开车，车上已经没乘客了\nsetTimeout(bus.start.bind(bus), 6666)\n```\n\n## 代理模式\n\n常用的虚拟代理形式：某一个花销很大的操作，可以通过虚拟代理的方式延迟到这种需要它的时候才去创建（例：使用虚拟代理实现图片懒加载）\n\n图片懒加载的方式：先通过一张 `loading` 图占位，然后通过异步的方式加载图片，等图片加载好了再把完成的图片加载到 `img` 标签里面。\n\n```js\nvar myImage = (function() {\n  var imgNode = document.createElement(\'img\')\n  document.body.appendChild(imgNode)\n  return function(src) {\n    imgNode.src = src\n  }\n})()\n\nvar ProxyImage = (function() {\n  var img = new Image()\n\n  img.onload = function() {\n    myImage(this.src)\n  }\n  return function(src) {\n    // 占位图片loading\n    myImage(\'http://www.baidu.com/img/baidu_jgylogo3.gif\')\n    img.src = src\n  }\n})()\n\n// 调用方式\nProxyImage(\'https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png\') // 真实要展示的图片\n```\n\n## 单例模式\n\n> 单例模式的定义：保证一个类仅有一个实例，并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否，如果存在则直接返回，如果不存在就创建了再返回，这就确保了一个类只有一个实例对象。\n\n```js\nclass CreateUser {\n  constructor(name) {\n    this.name = name\n    this.instance = null\n  }\n\n  getName() {\n    return this.name\n  }\n}\n\n// 代理实现单例模式\nvar ProxyMode = (function() {\n  var instance = null\n  return function(name) {\n    if (!instance) {\n      instance = new CreateUser(name)\n    }\n    return instance\n  }\n})()\n\nconst instanceA = new ProxyMode(\'instanceA\') // { name: \'instanceA\', instance: null }\nconst instanceB = new ProxyMode(\'instanceB\') // { name: \'instanceA\', instance: null }\n\nconsole.log(instanceA === instanceB) // true\n```\n\n### 优点\n\n1. 可以用来划分命名空间，减少全局变量的数量。\n2. 使用单体模式可以使代码组织的更为一致，使代码容易阅读和维护。\n3. 可以被实例化，且实例化一次。\n\n适用场景：一个单一对象。比如：弹窗，无论点击多少次，弹窗只应该被创建一次。', '2019-02-11 12:58:06', '2019-02-11 12:58:06');
INSERT INTO `article` VALUES (46, 'js - 创建对象的几种方式（工厂模式、构造函数模式、原型模式）', '# 创建对象的模式\n\n- [工厂模式](#工厂模式)\n  - 做法：**内部创建一个对象，并未对象属性赋值并且返回**\n  - 缺点：解决创建多个相识对象的问题，但不能识别创建的对象的类型\n- [构造函数模式](#构造函数模式)\n  - 做法：**直接将属性和方法赋值给 this 对象，没有 return 语句**\n  - 缺点：对象不是共用方法和属性，每 new 一次对象就要创建一个内存，超出性能消耗\n- [原型模式](#原型模式)\n  - 做法：**通过 prototype 为对象添加属性**\n  - 缺点：每个实例都共享属性方法，实例中修改对象属性会影响到其他实例\n- [组合使用构造函数模式和原型模式](#组合使用构造函数模式和原型模式)\n  - 做法：**构造函数模式用于定义实例属性，而原型模式用于定义共用方法**\n  - 构造函数与原型混成，使用较为广泛\n- [动态原型模式](#动态原型模式)\n  - 过检查某个应该存在的方法是否有效，来决定是否需要初始化原中的某个属性\n- 寄生构造函数模式...\n\n<!--more-->\n\n## 前言\n\njs 面向对象第一步是什么？答：创建对象。创建对象有很多中方式，我们最常用的是对象字面量来创建对象，`var obj = {}`，你看我这不就创建了一个对象了吗，我还干嘛要继续了解那些奇葩的方法呢？这么想的人活该单身，多掌握些找对象只有好处没有坏处哈。正经的，高阶上有这么一句话，使用对象字面量创建单个对象，有个明显的缺点，**使用同一个接口创建很多对象，会产生大量重复的代码。**为了解决这个问题，我们需要了解下面?这些方式。\n\n## 工厂模式\n\n```js\nfunction createPerson(name, age, job) {\n  var o = new Object()\n  o.name = name\n  o.age = age\n  o.job = job\n  o.sayName = function() {\n    alert(this.name)\n  }\n  return o\n}\nvar person1 = createPerson(\'Nicholas\', 29, \'Software Engineer\')\nvar person2 = createPerson(\'Greg\', 27, \'Doctor\')\n```\n\n工厂模式虽然解决了创建多个相似对象的问题，但却没有解决对象识别的问题法识别创建的对象的类型。因为全部都是 Object，没有区分度，不像 Date、Array 等，因此出现了构造函数模式\n\n## 构造函数模式\n\n```js\nfunction Person(name) {\n  this.name = name\n  this.showName = function() {\n    alert(this.name)\n  }\n}\nvar p1 = new Person(\'haha\')\np1.showName()\nvar p2 = new Person(\'hehe\')\np2.showName()\n```\n\n我们注意到， Person\\(\\) 中的代码  \n除了与 createPerson\\(\\) 中相同的部分外，还存在以下不同之处：\n\n- 没有显式地创建对象；\n- 直接将属性和方法赋给了 this 对象；\n- 没有 return 语句。\n\n此外，还应该注意到函数名 Person 使用的是大写字母 P。按照惯例，构造函数始终都应该以一个  \n大写字母开头，而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他 OO 语言，主要是为了  \n区别于 ECMAScript 中的其他函数；因为构造函数本身也是函数，只不过可以用来创建对象而已。  \n要创建 Person 的新实例，必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4  \n个步骤：\n\n1. 创建一个新对象；\n2. 将构造函数的作用域赋给新对象（因此 this 就指向了这个新对象）；\n3. 执行构造函数中的代码（为这个新对象添加属性）；\n4. 返回新对象。\n\n**举个例子**\n\n```js\nfunction Person(name) {\n  this.name = name\n  this.showName = function() {\n    alert(this.name)\n  }\n  console.log(this)\n}\nnew Person(\'haha\') //Person\nPerson(\'haha\') //window\n```\n\n我们会发现当用 New 去调用一个函数的时候，this 的指向会不一样。其实 New 主要做了下面这些事，不过下面写的只是大概的行为，并不是内部源码\n\n```js\nfunction Person(name) {\n  var obj = {} //声明一个空对象obj\n  obj._proto_ = Person.prototype\n  //把这个对象的_proto_属性指向构造函数的原型对象,这样obj就可以调用Person原型对象下的所有方法 ，这里原型先知道结论，下面会讲。\n  Person.apply(obj) //用apply方法让this指向obj对象\n  this.name = name //obj对象添加属性，方法\n  this.showName = function() {\n    alert(this.name)\n  }\n  return obj //返回这个对象\n}\n```\n\n**函数构造模式存在的问题：**\n\n```js\nalert(p1.showName == p2.showName) //false\n```\n\n**可见这两个对象并不是共用一个方法**，每 new 一次，系统都会新创建一个内存，这两个对象各自有各自的地盘，但他们具有相同的功能，还不共用，肯定不是我们所希望的。所以就有了下一种方法，原型+构造模式 ==&gt;原型模式\n\n## 原型模式\n\n我们创建的每个函数都有一个 `prototype` （原型）属性，**这个属性是一个指针，指向一个对象**，而这个对象的用途是包含可以由特定类型的**所有实例共享的属性和方法**。如果按照字面意思来理解，那  \n么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以  \n让所有对象实例共享它所包含的属性和方法。换句话说，不必在构造函数中定义对象实例的信息，而是  \n可以将这些信息直接添加到原型对象中，如下面的例子所示。\n\n```js\nfunction Person(name) {\n  this.name = name\n}\nPerson.prototype.showName = function() {\n  alert(this.name)\n}\nvar p1 = new Person(\'haha\')\np1.showName()\nvar p2 = new Person(\'hehe\')\np2.showName()\nalert(p1.showName == p2.showName) //true\n```\n\n测试为 true，可见 showName\\(\\)方法是共享的，也就是说他们共用一个内存，更进一步的说它们存在引用关系，也就是说你更改了 p1 的 showName 也会影响 p2 的 showName，而这个问题正是我们很少看到有人单独使用原型模式的原因所在。\n\n## 组合使用构造函数模式和原型模式\n\n创建自定义类型的最常见方式，就是组合使用构造函数模式与原型模式。构造函数模式用于定义实  \n例属性，而原型模式用于定义方法和共享的属性。结果，每个实例都会有自己的一份实例属性的副本，  \n但同时又共享着对方法的引用，最大限度地节省了内存。另外，这种混成模式还支持向构造函数传递参  \n数；可谓是集两种模式之长。\n\n```js\nfunction Person(name, age, job) {\n  this.name = name\n  this.age = age\n  this.job = job\n  this.friends = [\'Shelby\', \'Court\']\n}\nPerson.prototype = {\n  constructor: Person,\n  sayName: function() {\n    alert(this.name)\n  }\n}\nvar person1 = new Person(\'Nicholas\', 29, \'Software Engineer\')\nvar person2 = new Person(\'Greg\', 27, \'Doctor\')\nperson1.friends.push(\'Van\')\nalert(person1.friends) //\"Shelby,Count,Van\"\nalert(person2.friends) //\"Shelby,Count\"\nalert(person1.friends === person2.friends) //false\nalert(person1.sayName === person2.sayName) //true\n```\n\n在这个例子中，实例属性都是在构造函数中定义的，而由所有实例共享的属性 constructor 和方  \n法 sayName\\(\\) 则是在原型中定义的。而修改了 person1.friends （向其中添加一个新字符串），并不  \n会影响到 person2.friends ，因为它们分别引用了不同的数组。\n\n种构造函数与原型混成的模式，是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说，这是用来定义引用类型的一种默认模式。\n\n## 动态原型模式\n\n有其他 OO 语言经验的开发人员在看到独立的构造函数和原型时，很可能会感到非常困惑。动态原型模式正是致力于解决这个问题的一个方案，它把所有信息都封装在了构造函数中，而通过在构造函数中初始化原型（仅在必要的情况下），又保持了同时使用构造函数和原型的优点。换句话说，可以通过检查某个应该存在的方法是否有效，来决定是否需要初始化原型。来看一个例子。\n\n```js\nfunction Person(name, age, job) {\n  //属性\n  this.name = name\n  this.age = age\n  this.job = job\n  // 方法\n  if (typeof this.sayName != \'function\') {\n    Person.prototype.sayName = function() {\n      alert(this.name)\n    }\n  }\n}\nvar friend = new Person(\'Nicholas\', 29, \'Software Engineer\')\nfriend.sayName()\n```\n\n注意构造函数代码中 if 判断语句的部分。这里只在 sayName\\(\\) 方法不存在的情况下，才会将它添加到原  \n型中。这段代码只会在初次调用构造函数时才会执行。此后，原型已经完成初始化，不需要再做什么修  \n改了。不过要记住，这里对原型所做的修改，能够立即在所有实例中得到反映。因此，这种方法确实可  \n以说非常完美。其中， if 语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆  \nif 语句检查每个属性和每个方法；只要检查其中一个即可。对于采用这种模式创建的对象，还可以使  \n用 instanceof 操作符确定它的类型。\n\n> 使用动态原型模式时，不能使用对象字面量重写原型。如果  \n> 在已经创建了实例的情况下重写原型，那么就会切断现有实例与新原型之间的联系。\n\n```js\nfunction Person() {}\nvar friend = new Person()\nPerson.prototype = {\n  constructor: Person,\n  sayName: function() {\n    alert(this.name)\n  }\n}\nfriend.sayName() //error\n```\n\n## 寄生构造函数模式\n\n....\n', '2019-02-11 12:58:45', '2019-02-11 12:58:45');
INSERT INTO `article` VALUES (47, '[转] Javascript深入之bind的模拟实现', '## 构造函数创建对象\n\n我们先使用构造函数创建一个对象：\n\n```js\nfunction Person() {}\nvar person = new Person()\nperson.name = \'Kevin\'\nconsole.log(person.name) // Kevin\n```\n\n在这个例子中，`Person` 就是一个构造函数，我们使用 `new` 创建了一个实例对象 `person`。\n\n<!--more-->\n\n## prototype\n\n> 每一个函数都有一个 `prototype` 属性。\n\n```js\nfunction Person() {}\nPerson.prototype.name = \'Kevin\'\nvar person1 = new Person()\nvar person2 = new Person()\nconsole.log(person1.name) // Kevin\nconsole.log(person2.name) // Kevin\n```\n\n那这个函数的 `prototype` 属性到底指向的是什么呢？是这个函数的原型吗？\n\n其实，函数的 `prototype` 属性指向了一个对象，这个对象正是调用该构造函数而创建的实例的原型，也就是这个例子中的 `person1` 和 `person2` 的原型。\n\n那什么是原型呢？你可以这样理解：每一个 `JavaScript` 对象(`null` 除外)在创建的时候就会与之关联另一个对象，这个对象就是我们所说的原型，每一个对象都会从原型\"继承\"属性。\n\n让我们用一张图表示构造函数和实例原型之间的关系：\n\n![](https://github.com/mqyqingfeng/Blog/raw/master/Images/prototype1.png)\n\n在这张图中我们用 `Object.prototype` 表示实例原型。\n\n那么我们该怎么表示实例与实例原型，也就是 `person` 和 `Person.prototype` 之间的关系呢，这时候我们就要讲到第二个属性：\n\n## `__proto__`\n\n> 每一个实例都有一个 **`__proto__`** 指针，指向构造函数的原型对象。\n\n```js\nfunction Person() {}\nvar person = new Person()\nconsole.log(person.__proto__ === Person.prototype) // true\n```\n\n于是我们更新下关系图：\n\n![](https://github.com/mqyqingfeng/Blog/raw/master/Images/prototype2.png)\n\n既然实例对象和构造函数都可以指向原型，那么原型是否有属性指向构造函数或者实例呢？\n\n## constructor\n\n指向实例倒是没有，因为一个构造函数可以生成多个实例，但是原型指向构造函数倒是有的，这就要讲到第三个属性：`constructor`，每个原型都有一个 `constructor` 属性指向关联的构造函数。\n\n为了验证这一点，我们可以尝试：\n\n```js\nfunction Person() {}\nconsole.log(Person === Person.prototype.constructor) // true\n```\n\n所以再更新下关系图：\n\n![](https://github.com/mqyqingfeng/Blog/raw/master/Images/prototype3.png)\n\n综上我们已经得出：\n\n```js\nfunction Person() {}\nvar person = new Person()\n\nconsole.log(person.__proto__ == Person.prototype) // true\nconsole.log(Person.prototype.constructor == Person) // true\n\nconsole.log(Object.getPrototypeOf(person) === Person.prototype) /// 顺便学习一个ES5的方法,可以获得对象的原型 true\n```\n\n了解了构造函数、实例原型、和实例之间的关系，接下来我们讲讲实例和原型的关系：\n\n## 实例与原型\n\n当读取实例的属性时，如果找不到，就会查找与对象关联的原型中的属性，如果还查不到，就去找原型的原型，一直找到最顶层为止。\n\n```js\nfunction Person() {}\nPerson.prototype.name = \'Kevin\'\nvar person = new Person()\n\nperson.name = \'Daisy\'\nconsole.log(person.name) // Daisy\n\ndelete person.name\nconsole.log(person.name) // Kevin\n```\n\n在这个例子中，我们给实例对象 `person` 添加了 `name` 属性，当我们打印 `person.name` 的时候，结果自然为 `Daisy`。\n\n但是当我们删除了 `person` 的 `name` 属性时，读取 `person.name`，从 `person` 对象中找不到 `name` 属性就会从 `person` 的原型也就是 `person._proto_` ，也就是 `Person.prototype` 中查找，幸运的是我们找到了 `name` 属性，结果为 `Kevin`。\n\n但是万一还没有找到呢？原型的原型又是什么呢？\n\n## 原型的原型\n\n在前面，我们已经讲了原型也是一个对象，既然是对象，我们就可以用最原始的方式创建它，那就是：\n\n```js\nvar obj = new Object()\nobj.name = \'Kevin\'\nconsole.log(obj.name) // Kevin\n```\n\n其实原型对象就是通过 `Object` 构造函数生成的，结合之前所讲，实例的 `__proto__` 指向构造函数的 `prototype` ，所以我们再更新下关系图：\n\n![](https://github.com/mqyqingfeng/Blog/raw/master/Images/prototype4.png)\n\n## 原型链\n\n那 `Object.prototype` 的原型呢？\n\n```js\nObject.prototype.__proto__ === null // ture\n```\n\n然而 null 究竟代表了什么呢？\n\n引用阮一峰老师的 《[undefined 与 null 的区别](http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html)》 就是：\n\n> null 表示“没有对象”，即该处不应该有值。\n\n所以 `Object.prototype.__proto__` 的值为 `null` 跟 `Object.prototype` 没有原型，其实表达了一个意思。\n\n所以查找属性的时候查到 Object.prototype 就可以停止查找了。\n\n最后一张关系图也可以更新为：\n\n![](https://github.com/mqyqingfeng/Blog/raw/master/Images/prototype5.png)\n\n顺便还要说一下，图中由相互关联的原型组成的链状结构就是原型链，也就是蓝色的这条线。\n\n## 补充\n\n最后，补充三点大家可能不会注意的地方：\n\n### constructor\n\n```js\nfunction Person() {}\nvar person = new Person()\nconsole.log(person.constructor === Person) // true\n```\n\n当获取 `person.constructor` 时，其实 `person` 中并没有 `constructor` 属性,当不能读取到 `constructor` 属性时，会从 `person` 的原型也就是 `Person.prototype` 中读取，正好原型中有该属性，所以：\n\n```js\nperson.constructor === Person.prototype.constructor\n```\n\n### `__proto__`\n\n其次是 `__proto__` ，绝大部分浏览器都支持这个非标准的方法访问原型，然而它并不存在于 `Person.prototype` 中，实际上，它是来自于 `Object.prototype` ，与其说是一个属性，不如说是一个 `getter/setter`，当使用 obj.`__proto__` 时，可以理解成返回了 `Object.getPrototypeOf(obj)`。\n\n### 真的是继承吗？\n\n最后是关于继承，前面我们讲到“每一个对象都会从原型‘继承’属性”，实际上，继承是一个十分具有迷惑性的说法，引用《你不知道的 JavaScript》中的话，就是：\n\n继承意味着复制操作，然而 JavaScript 默认并不会复制对象的属性，相反，JavaScript 只是在两个对象之间创建一个关联，这样，一个对象就可以通过委托访问另一个对象的属性和函数，所以与其叫继承，委托的说法反而更准确些。\n\n## 来源\n\n- 转自 [冴羽 JavaScript 深入之从原型到原型链](https://github.com/mqyqingfeng/Blog/issues/2)', '2019-02-11 13:07:38', '2019-02-11 13:07:38');
INSERT INTO `article` VALUES (48, '[转] JavaScript深入之词法作用域和动态作用域', '# 作用域\n\n> 作用域是指程序源代码中定义变量的区域。\n\n作用域规定了如何查找变量，也就是确定当前执行代码对变量的访问权限。\n\n`JavaScript` 采用词法作用域(`lexical scoping`)，也就是静态作用域。\n\n## 静态作用域与动态作用域\n\n因为 `JavaScript` 采用的是词法作用域，**函数的作用域在函数定义的时候就决定了**。\n\n而与词法作用域相对的是动态作用域，函数的作用域是在函数调用的时候才决定的。\n\n让我们认真看个例子就能明白之间的区别：\n\n```js\nvar value = 1\n\nfunction foo() {\n  console.log(value)\n}\n\nfunction bar() {\n  var value = 2\n  foo()\n}\n\nbar() // 1\n```\n<!--more-->\n\n假设 JavaScript 采用静态作用域，让我们分析下执行过程：\n\n1. 执行 `foo` 函数，先从 `foo` 函数内部查找是否有局部变量 `value`\n2. 如果没有，就根据书写的位置，查找上面一层的代码，也就是 `value` 等于 1，所以结果会打印 1\n\n假设 JavaScript 采用动态作用域，让我们分析下执行过程：\n\n1. 执行 `foo` 函数，先从 `foo` 函数内部查找是否有局部变量 `value`\n2. 如果没有，就从调用函数的作用域，也就是 `bar` 函数内部查找 `value` 变量，所以结果会打印 2。\n\n前面我们已经说了，JavaScript 采用的是静态作用域，所以这个例子的结果是 1。\n\n## 动态作用域\n\n也许你会好奇什么语言是动态作用域？\n\n`bash` 就是动态作用域，不信的话，把下面的脚本存成例如 `scope.bash`，然后进入相应的目录，用命令行执行 `bash` `./scope.bash`，看看打印的值是多少。\n\n```js\nvalue=1\nfunction foo () {\n    echo $value;\n}\nfunction bar () {\n    local value=2;\n    foo;\n}\nbar\n```\n\n## 思考题\n\n最后，让我们看一个《JavaScript 权威指南》中的例子：\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f()\n}\ncheckscope() // local scope\n```\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f\n}\ncheckscope()() // local scope\n```\n\n两段代码都会打印：`local scope`。因为 JavaScript 采用的是词法作用域，函数的作用域基于函数创建的位置。\n\n而引用《JavaScript 权威指南》的回答就是：\n\nJavaScript 函数的执行用到了作用域链，这个作用域链是在函数定义的时候创建的。嵌套的函数 `f()` 定义在这个作用域链里，其中的变量 `scope` 一定是局部变量，不管何时何地执行函数 `f()`，这种绑定在执行 `f()` 时依然有效。\n\n但是在这里真正想让大家思考的是：\n\n虽然两段代码执行的结果一样，但是两段代码究竟有哪些不同呢？\n\n如果要回答这个问题，就要牵涉到很多的内容，词法作用域只是其中的一小部分，让我们期待下一篇文章————《JavaScript 深入之执行上下文栈》。', '2019-02-11 13:08:39', '2019-02-11 13:08:39');
INSERT INTO `article` VALUES (49, '[转] JavaScript深入之执行上下文栈', '## 顺序执行？\n\n如果要问到 JavaScript 代码执行顺序的话，想必写过 JavaScript 的开发者都会有个直观的印象，那就是顺序执行，毕竟：\n\n```js\nvar foo = function() {\n  console.log(\'foo1\')\n}\n\nfoo() // foo1\n\nvar foo = function() {\n  console.log(\'foo2\')\n}\n\nfoo() // foo2\n```\n\n然而去看这段代码：\n\n```js\nfunction foo() {\n  console.log(\'foo1\')\n}\n\nfoo() // foo2\n\nfunction foo() {\n  console.log(\'foo2\')\n}\n\nfoo() // foo2\n```\n\n打印的结果却是两个 `foo2`。\n刷过面试题的都知道这是因为 JavaScript 引擎并非一行一行地分析和执行程序，而是一段一段地分析执行。当执行一段代码的时候，会进行一个“准备工作”，比如第一个例子中的变量提升，和第二个例子中的函数提升。\n\n但是本文真正想让大家思考的是：这个“一段一段”中的“段”究竟是怎么划分的呢？\n\n到底 JavaScript 引擎遇到一段怎样的代码时才会做“准备工作”呢？\n\n<!--more-->\n\n## 可执行代码\n\n这就要说到 JavaScript 的可执行代码(`executable code`)的类型有哪些了？\n其实很简单，就三种，全局代码、函数代码、eval 代码。\n\n举个例子，当执行到一个函数的时候，就会进行准备工作，这里的“准备工作”，让我们用个更专业一点的说法，就叫做\"执行上下文(`execution context`)\"。\n\n## 执行上下文栈\n\n接下来问题来了，我们写的函数多了去了，如何管理创建的那么多执行上下文呢？\n\n所以 JavaScript 引擎创建了执行上下文栈（`Execution context stack，ECS`）来管理执行上下文\n\n为了模拟执行上下文栈的行为，让我们定义执行上下文栈是一个数组：\n\n```js\nECStack = []\n```\n\n试想当 JavaScript 开始要解释执行代码的时候，最先遇到的就是全局代码，所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文，我们用 `globalContext` 表示它，并且只有当整个应用程序结束的时候，`ECStack` 才会被清空，所以程序结束之前， `ECStack` 最底部永远有个 `globalContext`：\n\n```js\nECStack = [globalContext]\n```\n\n现在 JavaScript 遇到下面的这段代码了：\n\n```js\nfunction fun3() {\n  console.log(\'fun3\')\n}\n\nfunction fun2() {\n  fun3()\n}\n\nfunction fun1() {\n  fun2()\n}\n\nfun1()\n```\n\n当执行一个函数的时候，就会创建一个执行上下文，并且压入执行上下文栈，当函数执行完毕的时候，就会将函数的执行上下文从栈中弹出。知道了这样的工作原理，让我们来看看如何处理上面这段代码：\n\n```js\n// 伪代码\n\n// fun1()\nECStack.push(<fun1> functionContext);\n\n// fun1中竟然调用了fun2，还要创建fun2的执行上下文\nECStack.push(<fun2> functionContext);\n\n// 擦，fun2还调用了fun3！\nECStack.push(<fun3> functionContext);\n\n// fun3执行完毕\nECStack.pop();\n\n// fun2执行完毕\nECStack.pop();\n\n// fun1执行完毕\nECStack.pop();\n\n// javascript接着执行下面的代码，但是ECStack底层永远有个globalContext\n```\n\n## 解答思考题\n\n好啦，现在我们已经了解了执行上下文栈是如何处理执行上下文的，所以让我们看看上篇文章《JavaScript 深入之词法作用域和动态作用域》最后的问题：\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f()\n}\ncheckscope()\n```\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f\n}\ncheckscope()()\n```\n\n两段代码执行的结果一样，但是两段代码究竟有哪些不同呢？\n\n答案就是执行上下文栈的变化不一样。\n\n让我们模拟第一段代码：\n\n```js\nECStack.push(<checkscope> functionContext);\nECStack.push(<f> functionContext);\nECStack.pop();\nECStack.pop();\n```\n\n让我们模拟第二段代码：\n\n```js\nECStack.push(<checkscope> functionContext);\nECStack.pop();\nECStack.push(<f> functionContext);\nECStack.pop();\n```\n\n是不是有些不同呢？\n\n当然了，这样概括的回答执行上下文栈的变化不同，是不是依然有一种意犹未尽的感觉呢，为了更详细讲解两个函数执行上的区别，我们需要探究一下执行上下文到底包含了哪些内容，所以欢迎阅读下一篇《JavaScript 深入之变量对象》', '2019-02-11 13:15:43', '2019-02-11 13:15:43');
INSERT INTO `article` VALUES (50, '[转] JavaScript深入之变量对象', '在上篇《JavaScript 深入之执行上下文栈》中讲到，当 JavaScript 代码执行一段可执行代码(`executable code`)时，会创建对应的执行上下文(`execution context`)。\n\n对于每个执行上下文，都有三个重要属性：\n\n- 变量对象(Variable object，VO)\n- 作用域链(Scope chain)\n- this\n\n今天重点讲讲创建变量对象的过程。\n\n## 变量对象\n\n变量对象是与执行上下文相关的数据作用域，存储了在上下文中定义的变量和函数声明。\n\n因为不同执行上下文下的变量对象稍有不同，所以我们来聊聊全局上下文下的变量对象和函数上下文下的变量对象。\n\n<!--more-->\n\n## 全局上下文\n\n全局对象是预定义的对象，作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象，可以访问所有其他所有预定义的对象、函数和属性。\n\n> 全局对象是预定义的对象，作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象，可以访问所有其他所有预定义的对象、函数和属性。\n\n> 在顶层 JavaScript 代码中，可以用关键字 this 引用全局对象。因为全局对象是作用域链的头，这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。\n\n> 例如，当 JavaScript 代码引用 parseInt() 函数时，它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头，还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。\n\n如果看的不是很懂的话，容我再来介绍下全局对象:\n\n```js\n// 可以通过 this 引用，在客户端 JavaScript 中，全局对象就是 Window 对象\nconsole.log(this)\n\n// 全局对象是由 Object 构造函数实例化的一个对象。\nconsole.log(this instanceof Object)\n\n// 预定义了一堆，嗯，一大堆函数和属性\nconsole.log(Math.random())\nconsole.log(this.Math.random())\n\n// 作为全局变量的宿主。\nvar a = 1\nconsole.log(window.a)\n\n// 客户端 JavaScript 中，全局对象有 window 属性指向自身。\nthis.window.b = 2\nconsole.log(this.b)\n```\n\n花了一个大篇幅介绍全局对象，其实就想说：\n\n全局上下文中的变量对象就是全局对象呐！\n\n## 函数上下文\n\n在函数上下文中，我们用活动对象(`activation object`, `AO`)来表示变量对象。\n\n活动对象和变量对象其实是一个东西，只是变量对象是规范上的或者说是引擎实现上的，不可在 JavaScript 环境中访问，只有到当进入一个执行上下文中，这个执行上下文的变量对象才会被激活，所以才叫 `activation object` 呐，而只有被激活的变量对象，也就是活动对象上的各种属性才能被访问。\n\n活动对象是在进入函数上下文时刻被创建的，它通过函数的 `arguments` 属性初始化。`arguments` 属性值是 `Arguments` 对象。\n\n## 执行过程\n\n执行上下文的代码会分成两个阶段进行处理：分析和执行，我们也可以叫做：\n\n1. 进入执行上下文\n2. 代码执行\n\n### 进入执行上下文\n\n当进入执行上下文时，这时候还没有执行代码，\n变量对象会包括：\n\n1. 函数的所有形参 (如果是函数上下文)\n\n- 由名称和对应值组成的一个变量对象的属性被创建\n- 没有实参，属性值设为 undefined\n\n2. 函数声明\n\n- 由名称和对应值（函数对象(function-object)）组成一个变量对象的属性被创建\n- 如果变量对象已经存在相同名称的属性，则完全替换这个属性\n\n3. 变量声明\n\n- 由名称和对应值（undefined）组成一个变量对象的属性被创建；\n- 如果变量名称跟已经声明的形式参数或函数相同，则变量声明不会干扰已经存在的这类属性\n\n举个例子：\n\n```js\nfunction foo(a) {\n  var b = 2\n  function c() {}\n  var d = function() {}\n\n  b = 3\n}\n\nfoo(1)\n```\n\n在进入执行上下文后，这时候的 `AO` 是：\n\n```js\nAO = {\n    arguments: {\n        0: 1,\n        length: 1\n    },\n    a: 1,\n    b: undefined,\n    c: reference to function c(){},\n    d: undefined\n}\n```\n\n### 代码执行\n\n在代码执行阶段，会顺序执行代码，根据代码，修改变量对象的值\n\n还是上面的例子，当代码执行完后，这时候的 AO 是：\n\n```js\nAO = {\n    arguments: {\n        0: 1,\n        length: 1\n    },\n    a: 1,\n    b: 3,\n    c: reference to function c(){},\n    d: reference to FunctionExpression \"d\"\n}\n```\n\n到这里变量对象的创建过程就介绍完了，让我们简洁的总结我们上述所说：\n\n1. 全局上下文的变量对象初始化是全局对象\n\n2. 函数上下文的变量对象初始化只包括 Arguments 对象\n\n3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值\n\n4. 在代码执行阶段，会再次修改变量对象的属性值\n\n## 思考题\n\n最后让我们看几个例子：\n\n1.第一题\n\n```js\nfunction foo() {\n  console.log(a)\n  a = 1\n}\n\nfoo() // ???\n\nfunction bar() {\n  a = 1\n  console.log(a)\n}\nbar() // ???\n```\n\n第一段会报错：`Uncaught ReferenceError: a is not defined`。\n\n第二段会打印：`1`。\n\n这是因为函数中的 \"a\" 并没有通过 var 关键字声明，所有不会被存放在 AO 中。\n\n第一段执行 console 的时候， AO 的值是：\n\n```js\nAO = {\n  arguments: {\n    length: 0\n  }\n}\n```\n\n没有 a 的值，然后就会到全局去找，全局也没有，所以会报错。\n\n当第二段执行 console 的时候，全局对象已经被赋予了 a 属性，这时候就可以从全局找到 a 的值，所以会打印 1。\n\n2.第二题\n\n```js\nconsole.log(foo)\n\nfunction foo() {\n  console.log(\'foo\')\n}\n\nvar foo = 1\n```\n\n会打印函数，而不是 undefined 。\n\n这是因为在进入执行上下文时，首先会处理函数声明，其次会处理变量声明，如果如果变量名称跟已经声明的形式参数或函数相同，则变量声明不会干扰已经存在的这类属性。', '2019-02-11 13:16:26', '2019-02-11 13:16:26');
INSERT INTO `article` VALUES (51, '[转] JavaScript深入之作用域链', '## JavaScript 深入之作用域链\n\n> JavaScript 深入系列第五篇，讲述作用链的创建过程，最后结合着变量对象，执行上下文栈，让我们一起捋一捋函数创建和执行的过程中到底发生了什么？\n\n## 前言\n\n在[《JavaScript 深入之执行上下文栈》](/)中讲到，当 JavaScript 代码执行一段可执行代码(`executable code`)时，会创建对应的执行上下文(`execution context`)。\n\n对于每个执行上下文，都有三个重要属性：\n\n- 变量对象(Variable object，VO)\n- 作用域链(Scope chain)\n- this\n\n今天重点讲讲作用域链。\n\n<!--more-->\n\n## 作用域链\n\n在[《JavaScript 深入之变量对象》](/)中讲到，当查找变量的时候，会先从当前上下文的变量对象中查找，如果没有找到，就会从父级(词法层面上的父级)执行上下文的变量对象中查找，一直找到全局上下文的变量对象，也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。\n\n下面，让我们以一个函数的创建和激活两个时期来讲解作用域链是如何创建和变化的。\n\n## 函数创建\n\n在[《JavaScript 深入之词法作用域和动态作用域》](/)中讲到，函数的作用域在函数定义的时候就决定了。\n\n这是因为函数有一个内部属性 `[[scope]]`，当函数创建的时候，就会保存所有父变量对象到其中，你可以理解 `[[scope]]` 就是所有父变量对象的层级链，但是注意：`[[scope]]` 并不代表完整的作用域链！\n\n举个例子：\n\n```js\n\nfunction foo() {\n    function bar() {\n        ...\n    }\n}\n\n```\n\n函数创建时，各自的`[[scope]]`为：\n\n```js\n\nfoo.[[scope]] = [\n  globalContext.VO\n];\n\nbar.[[scope]] = [\n    fooContext.AO,\n    globalContext.VO\n];\n\n```\n\n## 函数激活\n\n当函数激活时，进入函数上下文，创建 `VO/AO` 后，就会将活动对象添加到作用链的前端。\n\n这时候执行上下文的作用域链，我们命名为 `Scope`：\n\n```js\nScope = [AO].concat([[Scope]])\n```\n\n至此，作用域链创建完毕。\n\n## 捋一捋\n\n以下面的例子为例，结合着之前讲的变量对象和执行上下文栈，我们来总结一下函数执行上下文中作用域链和变量对象的创建过程：\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope2 = \'local scope\'\n  return scope2\n}\ncheckscope()\n```\n\n执行过程如下：\n\n1.`checkscope` 函数被创建，保存作用域链到 内部属性`[[scope]]`\n\n```js\ncheckscope.[[scope]] = [\n    globalContext.VO\n];\n```\n\n2.执行 `checkscope` 函数，创建 `checkscope` 函数执行上下文，`checkscope` 函数执行上下文被压入执行上下文栈\n\n```js\nECStack = [checkscopeContext, globalContext]\n```\n\n3.`checkscope` 函数并不立刻执行，开始做准备工作，第一步：复制函数`[[scope]]`属性创建作用域链\n\n```js\ncheckscopeContext = {\n    Scope: checkscope.[[scope]],\n}\n```\n\n4.第二步：用 `arguments` 创建活动对象，随后初始化活动对象，加入形参、函数声明、变量声明\n\n```js\ncheckscopeContext = {\n  AO: {\n    arguments: {\n      length: 0\n    },\n    scope2: undefined\n  }\n}\n```\n\n5.第三步：将活动对象压入 `checkscope` 作用域链顶端\n\n```js\ncheckscopeContext = {\n  AO: {\n    arguments: {\n      length: 0\n    },\n    scope2: undefined\n  },\n  Scope: [AO, [[Scope]]]\n}\n```\n\n6.准备工作做完，开始执行函数，随着函数的执行，修改`AO`的属性值\n\n```js\ncheckscopeContext = {\n  AO: {\n    arguments: {\n      length: 0\n    },\n    scope2: \'local scope\'\n  },\n  Scope: [AO, [[Scope]]]\n}\n```\n\n7.查找到 `scope2` 的值，返回后函数执行完毕，函数上下文从执行上下文栈中弹出\n\n```js\nECStack = [globalContext]\n```\n\n## 本文相关链接\n\n- [《JavaScript 深入之词法作用域和动态作用域》](https://github.com/mqyqingfeng/Blog/issues/3)\n- [《JavaScript 深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)\n- [《JavaScript 深入之变量对象》](https://github.com/mqyqingfeng/Blog/issues/5)\n', '2019-02-11 13:17:04', '2019-02-11 13:17:04');
INSERT INTO `article` VALUES (52, '[转] JavaScript深入之从 ECMAScript 规范解读 this', '## 前言\n\n在[《JavaScript 深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)中讲到，当 JavaScript 代码执行一段可执行代码(executable code)时，会创建对应的执行上下文(execution context)。\n\n对于每个执行上下文，都有三个重要属性\n\n- 变量对象(Variable object，VO)\n- 作用域链(Scope chain)\n- this\n\n今天重点讲讲 this，然而不好讲。\n\n……\n\n因为我们要从 ECMASciript5 规范开始讲起。\n\n先奉上 ECMAScript 5.1 规范地址：\n\n英文版：[http://es5.github.io/#x15.1](http://es5.github.io/#x15.1)\n\n中文版：[http://yanhaijing.com/es5/#115](http://yanhaijing.com/es5/#115)\n\n让我们开始了解规范吧！\n\n<!--more-->\n\n## Types\n\n首先是第 8 章 Types：\n\n> Types are further subclassified into ECMAScript language types and specification types.\n\n> An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.\n\n> A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.\n\n我们简单的翻译一下：\n\nECMAScript 的类型分为语言类型和规范类型。\n\nECMAScript 语言类型是开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的 Undefined, Null, Boolean, String, Number, 和 Object。\n\n而规范类型相当于 meta-values，是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括：Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。\n\n没懂？没关系，我们只要知道在 ECMAScript 规范中还有一种只存在于规范中的类型，它们的作用是用来描述语言底层行为逻辑。\n\n今天我们要讲的重点是便是其中的 Reference 类型。它与 this 的指向有着密切的关联。\n\n## Reference\n\n那什么又是 `Reference` ？\n\n让我们看 8.7 章 The Reference Specification Type：\n\n> The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.\n\n所以 `Reference` 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的。\n\n抄袭尤雨溪大大的话，就是：\n\n> 这里的 `Reference` 是一个 Specification Type，也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的，但并不存在于实际的 js 代码中。\n\n再看接下来的这段具体介绍 `Reference` 的内容：\n\n> A Reference is a resolved name binding.\n\n> A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag.\n\n> The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).\n\n> A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.\n\n这段讲述了 Reference 的构成，由三个组成部分，分别是：\n\n- base value\n- referenced name\n- strict reference\n\n可是这些到底是什么呢？\n\n我们简单的理解的话：\n\nbase value 就是属性所在的对象或者就是 EnvironmentRecord，它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。\n\nreferenced name 就是属性的名称。\n\n举个例子：\n\n```js\nvar foo = 1\n\n// 对应的Reference是：\nvar fooReference = {\n  base: EnvironmentRecord,\n  name: \'foo\',\n  strict: false\n}\n```\n\n再举个例子：\n\n```js\nvar foo = {\n  bar: function() {\n    return this\n  }\n}\n\nfoo.bar() // foo\n\n// bar对应的Reference是：\nvar BarReference = {\n  base: foo,\n  propertyName: \'bar\',\n  strict: false\n}\n```\n\n而且规范中还提供了获取 Reference 组成部分的方法，比如 GetBase 和 IsPropertyReference。\n\n这两个方法很简单，简单看一看：\n\n1.GetBase\n\n> GetBase(V). Returns the base value component of the reference V.\n\n返回 reference 的 base value。\n\n2.IsPropertyReference\n\n> IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.\n\n简单的理解：如果 base value 是一个对象，就返回 true。\n\n## GetValue\n\n除此之外，紧接着在 8.7.1 章规范中就讲了一个用于从 Reference 类型获取对应值的方法： GetValue。\n\n简单模拟 GetValue 的使用：\n\n```js\nvar foo = 1\n\nvar fooReference = {\n  base: EnvironmentRecord,\n  name: \'foo\',\n  strict: false\n}\n\nGetValue(fooReference) // 1;\n```\n\nGetValue 返回对象属性真正的值，但是要注意：\n\n**调用 GetValue，返回的将是具体的值，而不再是一个 Reference**\n\n这个很重要，这个很重要，这个很重要。\n\n## 如何确定 this 的值\n\n关于 Reference 讲了那么多，为什么要讲 Reference 呢？到底 Reference 跟本文的主题 this 有哪些关联呢？如果你能耐心看完之前的内容，以下开始进入高能阶段：\n\n看规范 11.2.3 Function Calls：\n\n这里讲了当函数调用的时候，如何确定 this 的取值。\n\n只看第一步、第六步、第七步：\n\n> 1.Let _ref_ be the result of evaluating MemberExpression.\n\n> 6.If Type(_ref_) is Reference, then\n\n>       a.If IsPropertyReference(ref) is true, then\n\n>           i.Let thisValue be GetBase(ref).\n\n>       b.Else, the base of ref is an Environment Record\n\n>           i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).\n>\n> 7.Else, Type(_ref_) is not Reference.\n\n>       a. Let thisValue be undefined.\n\n让我们描述一下：\n\n1.计算 MemberExpression 的结果赋值给 ref\n\n2.判断 ref 是不是一个 Reference 类型\n\n    2.1 如果 ref 是 Reference，并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)\n\n    2.2 如果 ref 是 Reference，并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)\n\n    2.3 如果 ref 不是 Reference，那么 this 的值为 undefined\n\n## 具体分析\n\n让我们一步一步看：\n\n1. 计算 MemberExpression 的结果赋值给 ref\n\n什么是 MemberExpression？看规范 11.2 Left-Hand-Side Expressions：\n\nMemberExpression :\n\n- PrimaryExpression // 原始表达式 可以参见《JavaScript 权威指南第四章》\n- FunctionExpression // 函数定义表达式\n- MemberExpression [ Expression ] // 属性访问表达式\n- MemberExpression . IdentifierName // 属性访问表达式\n- new MemberExpression Arguments // 对象创建表达式\n\n举个例子：\n\n```js\nfunction foo() {\n  console.log(this)\n}\n\nfoo() // MemberExpression 是 foo\n\nfunction foo() {\n  return function() {\n    console.log(this)\n  }\n}\n\nfoo()() // MemberExpression 是 foo()\n\nvar foo = {\n  bar: function() {\n    return this\n  }\n}\n\nfoo.bar() // MemberExpression 是 foo.bar\n```\n\n所以简单理解 MemberExpression 其实就是()左边的部分。\n\n2.判断 ref 是不是一个 Reference 类型。\n\n关键就在于看规范是如何处理各种 MemberExpression，返回的结果是不是一个 Reference 类型。\n\n举最后一个例子：\n\n```js\nvar value = 1\n\nvar foo = {\n  value: 2,\n  bar: function() {\n    return this.value\n  }\n}\n\n//示例1\nconsole.log(foo.bar())\n//示例2\nconsole.log(foo.bar())\n//示例3\nconsole.log((foo.bar = foo.bar)())\n//示例4\nconsole.log((false || foo.bar)())\n//示例5\nconsole.log((foo.bar, foo.bar)())\n```\n\n### foo.bar()\n\n在示例 1 中，MemberExpression 计算的结果是 foo.bar，那么 foo.bar 是不是一个 Reference 呢？\n\n查看规范 11.2.1 Property Accessors，这里展示了一个计算的过程，什么都不管了，就看最后一步：\n\n> Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.\n\n我们得知该表达式返回了一个 Reference 类型！\n\n根据之前的内容，我们知道该值为：\n\n```js\nvar Reference = {\n  base: foo,\n  name: \'bar\',\n  strict: false\n}\n```\n\n接下来按照 2.1 的判断流程走：\n\n> 2.1 如果 ref 是 Reference，并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)\n\n该值是 Reference 类型，那么 IsPropertyReference(ref) 的结果是多少呢？\n\n前面我们已经铺垫了 IsPropertyReference 方法，如果 base value 是一个对象，结果返回 true。\n\nbase value 为 foo，是一个对象，所以 IsPropertyReference(ref) 结果为 true。\n\n这个时候我们就可以确定 this 的值了：\n\n```js\nthis = GetBase(ref)，\n```\n\nGetBase 也已经铺垫了，获得 base value 值，这个例子中就是 foo，所以 this 的值就是 foo ，示例 1 的结果就是 2！\n\n唉呀妈呀，为了证明 this 指向 foo，真是累死我了！但是知道了原理，剩下的就更快了。\n\n### (foo.bar)()\n\n看示例 2：\n\n```js\nconsole.log(foo.bar())\n```\n\nfoo.bar 被 () 包住，查看规范 11.1.6 The Grouping Operator\n\n直接看结果部分：\n\n> Return the result of evaluating Expression. This may be of type Reference.\n\n> NOTE This algorithm does not apply GetValue to the result of evaluating Expression.\n\n实际上 () 并没有对 MemberExpression 进行计算，所以其实跟示例 1 的结果是一样的。\n\n### (foo.bar = foo.bar)()\n\n看示例 3，有赋值操作符，查看规范 11.13.1 Simple Assignment ( = ):\n\n计算的第三步：\n\n> 3.Let rval be GetValue(rref).\n\n因为使用了 GetValue，所以返回的值不是 Reference 类型，\n\n按照之前讲的判断逻辑：\n\n> 2.3 如果 ref 不是 Reference，那么 this 的值为 undefined\n\nthis 为 undefined，非严格模式下，this 的值为 undefined 的时候，其值会被隐式转换为全局对象。\n\n### (false || foo.bar)()\n\n看示例 4，逻辑与算法，查看规范 11.11 Binary Logical Operators：\n\n计算第二步：\n\n> 2.Let lval be GetValue(lref).\n\n因为使用了 GetValue，所以返回的不是 Reference 类型，this 为 undefined\n\n### (foo.bar, foo.bar)()\n\n看示例 5，逗号操作符，查看规范 11.14 Comma Operator ( , )\n\n计算第二步：\n\n> 2.Call GetValue(lref).\n\n因为使用了 GetValue，所以返回的不是 Reference 类型，this 为 undefined\n\n### 揭晓结果\n\n所以最后一个例子的结果是：\n\n```js\nvar value = 1\n\nvar foo = {\n  value: 2,\n  bar: function() {\n    return this.value\n  }\n}\n\n//示例1\nconsole.log(foo.bar()) // 2\n//示例2\nconsole.log(foo.bar()) // 2\n//示例3\nconsole.log((foo.bar = foo.bar)()) // 1\n//示例4\nconsole.log((false || foo.bar)()) // 1\n//示例5\nconsole.log((foo.bar, foo.bar)()) // 1\n```\n\n注意：以上是在非严格模式下的结果，严格模式下因为 this 返回 undefined，所以示例 3 会报错。\n\n### 补充\n\n最最后，忘记了一个最最普通的情况：\n\n```js\nfunction foo() {\n  console.log(this)\n}\n\nfoo()\n```\n\nMemberExpression 是 foo，解析标识符，查看规范 10.3.1 Identifier Resolution，会返回一个 Reference 类型的值：\n\n```js\nvar fooReference = {\n  base: EnvironmentRecord,\n  name: \'foo\',\n  strict: false\n}\n```\n\n接下来进行判断：\n\n> 2.1 如果 ref 是 Reference，并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)\n\n因为 base value 是 EnvironmentRecord，并不是一个 Object 类型，还记得前面讲过的 base value 的取值可能吗？ 只可能是 undefined, an Object, a Boolean, a String, a Number, 和 an environment record 中的一种。\n\nIsPropertyReference(ref) 的结果为 false，进入下个判断：\n\n> 2.2 如果 ref 是 Reference，并且 base value 值是 Environment Record, 那么 this 的值为 ImplicitThisValue(ref)\n\nbase value 正是 Environment Record，所以会调用 ImplicitThisValue(ref)\n\n查看规范 10.2.1.1.6，ImplicitThisValue 方法的介绍：该函数始终返回 undefined。\n\n所以最后 this 的值就是 undefined。\n\n## 多说一句\n\n尽管我们可以简单的理解 this 为调用函数的对象，如果是这样的话，如何解释下面这个例子呢？\n\n```js\nvar value = 1\n\nvar foo = {\n  value: 2,\n  bar: function() {\n    return this.value\n  }\n}\nconsole.log((false || foo.bar)()) // 1\n```\n\n此外，又如何确定调用函数的对象是谁呢？在写文章之初，我就面临着这些问题，最后还是放弃从多个情形下给大家讲解 this 指向的思路，而是追根溯源的从 ECMASciript 规范讲解 this 的指向，尽管从这个角度写起来和读起来都比较吃力，但是一旦多读几遍，明白原理，绝对会给你一个全新的视角看待 this 。而你也就能明白，尽管 foo() 和 (foo.bar = foo.bar)() 最后结果都指向了 undefined，但是两者从规范的角度上却有着本质的区别。\n\n此篇讲解执行上下文的 this，即便不是很理解此篇的内容，依然不影响大家了解执行上下文这个主题下其他的内容。所以，依然可以安心的看下一篇文章。', '2019-02-11 13:18:00', '2019-02-11 13:18:00');
INSERT INTO `article` VALUES (53, '[转] JavaScript深入之执行上下文', '> JavaScript 深入系列第七篇，结合之前所讲的四篇文章，以权威指南的 demo 为例，具体讲解当函数执行的时候，执行上下文栈、变量对象、作用域链是如何变化的。\n\n## 前言\n\n在[《JavaScript 深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)中讲到，当 JavaScript 代码执行一段可执行代码(executable code)时，会创建对应的执行上下文(execution contexts)。\n\n对于每个执行上下文，都有三个重要属性：\n\n- 变量对象(`Variable object`，`VO`)\n- 作用域链(`Scope chain`)\n- `this`\n\n然后分别在[《JavaScript 深入之变量对象》](https://github.com/mqyqingfeng/Blog/issues/5)、[《JavaScript 深入之作用域链》](https://github.com/mqyqingfeng/Blog/issues/6)、[《JavaScript 深入之从 ECMAScript 规范解读 this》](https://github.com/mqyqingfeng/Blog/issues/7)中讲解了这三个属性。\n\n阅读本文前，如果对以上的概念不是很清楚，希望先阅读这些文章。\n\n因为，这一篇，我们会结合着所有内容，讲讲执行上下文的具体处理过程。\n\n<!--more-->\n\n## 思考题\n\n在[《JavaScript 深入之词法作用域和动态作用域》](https://github.com/mqyqingfeng/Blog/issues/3)中，提出这样一道思考题：\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f()\n}\ncheckscope()\n```\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f\n}\ncheckscope()()\n```\n\n两段代码都会打印\'local scope\'。虽然两段代码执行的结果一样，但是两段代码究竟有哪些不同呢？\n\n紧接着就在下一篇[《JavaScript 深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)中，讲到了两者的区别在于执行上下文栈的变化不一样，然而，如果是这样笼统的回答，依然显得不够详细，本篇就会详细的解析执行上下文栈和执行上下文的具体变化过程。\n\n## 具体执行分析\n\n我们分析第一段代码：\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f()\n}\ncheckscope()\n```\n\n执行过程如下：\n\n1.执行全局代码，创建全局执行上下文，全局上下文被压入执行上下文栈\n\n```js\nECStack = [globalContext]\n```\n\n2.全局上下文初始化\n\n```js\nglobalContext = {\n  VO: [global, scope, checkscope],\n  Scope: [globalContext.VO],\n  this: globalContext.VO\n}\n```\n\n2.初始化的同时，`checkscope` 函数被创建，保存作用域链到函数的内部属性`[[scope]]`\n\n```js\n    checkscope.[[scope]] = [\n      globalContext.VO\n    ];\n```\n\n3.执行 `checkscope` 函数，创建 `checkscope` 函数执行上下文，`checkscope` 函数执行上下文被压入执行上下文栈\n\n```js\nECStack = [checkscopeContext, globalContext]\n```\n\n4.checkscope 函数执行上下文初始化：\n\n1. 复制函数 `[[scope]]` 属性创建作用域链，\n2. 用 `arguments` 创建活动对象，\n3. 初始化活动对象，即加入形参、函数声明、变量声明，\n4. 将活动对象压入 `checkscope` 作用域链顶端。\n\n同时 f 函数被创建，保存作用域链到 f 函数的内部属性`[[scope]]`\n\n```js\n    checkscopeContext = {\n        AO: {\n            arguments: {\n                length: 0\n            },\n            scope: undefined,\n            f: reference to function f(){}\n        },\n        Scope: [AO, globalContext.VO],\n        this: undefined\n    }\n```\n\n5.执行 f 函数，创建 f 函数执行上下文，f 函数执行上下文被压入执行上下文栈\n\n```js\nECStack = [fContext, checkscopeContext, globalContext]\n```\n\n6.f 函数执行上下文初始化, 以下跟第 4 步相同：\n\n1. 复制函数 `[[scope]]` 属性创建作用域链\n2. 用 `arguments` 创建活动对象\n3. 初始化活动对象，即加入形参、函数声明、变量声明\n4. 将活动对象压入 f 作用域链顶端\n\n```js\nfContext = {\n  AO: {\n    arguments: {\n      length: 0\n    }\n  },\n  Scope: [AO, checkscopeContext.AO, globalContext.VO],\n  this: undefined\n}\n```\n\n7.f 函数执行，沿着作用域链查找 `scope` 值，返回 `scope` 值\n\n8.f 函数执行完毕，f 函数上下文从执行上下文栈中弹出\n\n```js\nECStack = [checkscopeContext, globalContext]\n```\n\n9.`checkscope` 函数执行完毕，`checkscope` 执行上下文从执行上下文栈中弹出\n\n```js\nECStack = [globalContext]\n```\n\n第二段代码就留给大家去尝试模拟它的执行过程。\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f\n}\ncheckscope()()\n```\n\n不过，在下一篇《JavaScript 深入之闭包》中也会提及这段代码的执行过程。\n\n## 下一篇文章\n\n[《JavaScript 深入之闭包》](https://github.com/mqyqingfeng/Blog/issues/9)\n\n## 相关链接\n\n[《JavaScript 深入之词法作用域和动态作用域》](https://github.com/mqyqingfeng/Blog/issues/3)\n\n[《JavaScript 深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)\n\n[《JavaScript 深入之变量对象》](https://github.com/mqyqingfeng/Blog/issues/5)\n\n[《JavaScript 深入之作用域链》](https://github.com/mqyqingfeng/Blog/issues/6)\n\n[《JavaScript 深入之从 ECMAScript 规范解读 this》](https://github.com/mqyqingfeng/Blog/issues/7)\n\n## 重要参考\n\n[《一道 js 面试题引发的思考》](https://github.com/kuitos/kuitos.github.io/issues/18)', '2019-02-11 13:59:53', '2019-02-11 13:59:53');
INSERT INTO `article` VALUES (54, '[转] JavaScript深入之闭包', '## 定义\n\nMDN 对闭包的定义为：\n\n> 闭包是指那些能够访问自由变量的函数。\n\n那什么是自由变量呢？\n\n> 自由变量是指在函数中使用的，但既不是函数参数也不是函数的局部变量的变量。\n\n由此，我们可以看出闭包共有两部分组成：\n\n> 闭包 = 函数 + 函数能够访问的自由变量\n\n举个例子：\n\n```js\nvar a = 1\n\nfunction foo() {\n  console.log(a)\n}\n\nfoo()\n```\n\n`foo` 函数可以访问变量 a，但是 a 既不是 `foo` 函数的局部变量，也不是 `foo` 函数的参数，所以 a 就是自由变量。\n\n那么，函数 `foo` + `foo` 函数访问的自由变量 a 不就是构成了一个闭包嘛……\n\n<!--more-->\n\n还真是这样的！\n\n所以在《JavaScript 权威指南》中就讲到：从技术的角度讲，所有的 JavaScript 函数都是闭包。\n\n咦，这怎么跟我们平时看到的讲到的闭包不一样呢！？\n\n别着急，这是理论上的闭包，其实还有一个实践角度上的闭包，让我们看看汤姆大叔翻译的关于闭包的文章中的定义：\n\nECMAScript 中，闭包指的是：\n\n1. 从理论角度：所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此，因为函数中访问全局变量就相当于是在访问自由变量，这个时候使用最外层的作用域。\n2. 从实践角度：以下函数才算是闭包：\n   1. 即使创建它的上下文已经销毁，它仍然存在（比如，内部函数从父函数中返回）\n   2. 在代码中引用了自由变量\n\n接下来就来讲讲实践上的闭包。\n\n## 分析\n\n让我们先写个例子，例子依然是来自《JavaScript 权威指南》，稍微做点改动：\n\n```js\nvar scope = \'global scope\'\nfunction checkscope() {\n  var scope = \'local scope\'\n  function f() {\n    return scope\n  }\n  return f\n}\n\nvar foo = checkscope()\nfoo()\n```\n\n首先我们要分析一下这段代码中执行上下文栈和执行上下文的变化情况。\n\n另一个与这段代码相似的例子，在[《JavaScript 深入之执行上下文》](https://github.com/mqyqingfeng/Blog/issues/8)中有着非常详细的分析。如果看不懂以下的执行过程，建议先阅读这篇文章。\n\n这里直接给出简要的执行过程：\n\n1. 进入全局代码，创建全局执行上下文，全局执行上下文压入执行上下文栈\n2. 全局执行上下文初始化\n3. 执行 `checkscope` 函数，创建 `checkscope` 函数执行上下文，`checkscope` 执行上下文被压入执行上下文栈\n4. `checkscope` 执行上下文初始化，创建变量对象、作用域链、this 等\n5. `checkscope` 函数执行完毕，`checkscope` 执行上下文从执行上下文栈中弹出\n6. 执行 f 函数，创建 f 函数执行上下文，f 执行上下文被压入执行上下文栈\n7. f 执行上下文初始化，创建变量对象、作用域链、this 等\n8. f 函数执行完毕，f 函数上下文从执行上下文栈中弹出\n\n了解到这个过程，我们应该思考一个问题，那就是：\n\n当 f 函数执行的时候，`checkscope` 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出)，怎么还会读取到 `checkscope` 作用域下的 `scope` 值呢？\n\n以上的代码，要是转换成 `PHP`，就会报错，因为在 `PHP` 中，f 函数只能读取到自己作用域和全局作用域里的值，所以读不到 `checkscope` 下的 `scope` 值。(这段我问的 PHP 同事……)\n\n然而 JavaScript 却是可以的！\n\n当我们了解了具体的执行过程后，我们知道 f 执行上下文维护了一个作用域链：\n\n```js\nfContext = {\n  Scope: [AO, checkscopeContext.AO, globalContext.VO]\n}\n```\n\n对的，就是因为这个作用域链，f 函数依然可以读取到 checkscopeContext.AO 的值，说明当 f 函数引用了 checkscopeContext.AO 中的值的时候，即使 checkscopeContext 被销毁了，但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中，f 函数依然可以通过 f 函数的作用域链找到它，正是因为 JavaScript 做到了这一点，从而实现了闭包这个概念。\n\n所以，让我们再看一遍实践角度上闭包的定义：\n\n1. 即使创建它的上下文已经销毁，它仍然存在（比如，内部函数从父函数中返回）\n2. 在代码中引用了自由变量\n\n在这里再补充一个《JavaScript 权威指南》英文原版对闭包的定义:\n\n> This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.\n\n闭包在计算机科学中也只是一个普通的概念，大家不要去想得太复杂。\n\n## 必刷题\n\n接下来，看这道刷题必刷，面试必考的闭包题：\n\n```js\nvar data = []\n\nfor (var i = 0; i < 3; i++) {\n  data[i] = function() {\n    console.log(i)\n  }\n}\n\ndata[0]()\ndata[1]()\ndata[2]()\n```\n\n答案是都是 3，让我们分析一下原因：\n\n当执行到 data[0] 函数之前，此时全局上下文的 VO 为：\n\n```js\nglobalContext = {\n    VO: {\n        data: [...],\n        i: 3\n    }\n}\n```\n\n当执行 data[0] 函数的时候，data[0] 函数的作用域链为：\n\n```js\ndata[0]Context = {\n    Scope: [AO, globalContext.VO]\n}\n```\n\ndata[0]Context 的 AO 并没有 i 值，所以会从 globalContext.VO 中查找，i 为 3，所以打印的结果就是 3。\n\ndata[1] 和 data[2] 是一样的道理。\n\n所以让我们改成闭包看看：\n\n```js\nvar data = []\n\nfor (var i = 0; i < 3; i++) {\n  data[i] = (function(i) {\n    return function() {\n      console.log(i)\n    }\n  })(i)\n}\n\ndata[0]()\ndata[1]()\ndata[2]()\n```\n\n当执行到 data[0] 函数之前，此时全局上下文的 VO 为：\n\n```js\nglobalContext = {\n    VO: {\n        data: [...],\n        i: 3\n    }\n}\n```\n\n跟没改之前一模一样。\n\n当执行 data[0] 函数的时候，data[0] 函数的作用域链发生了改变：\n\n```js\ndata[0]Context = {\n    Scope: [AO, 匿名函数Context.AO globalContext.VO]\n}\n```\n\n匿名函数执行上下文的 AO 为：\n\n```js\n匿名函数Context = {\n  AO: {\n    arguments: {\n      0: 0,\n      length: 1\n    },\n    i: 0\n  }\n}\n```\n\ndata[0]Context 的 AO 并没有 i 值，所以会沿着作用域链从匿名函数 Context.AO 中查找，这时候就会找 i 为 0，找到了就不会往 globalContext.VO 中查找了，即使 globalContext.VO 也有 i 的值(值为 3)，所以打印的结果就是 0。\n\ndata[1] 和 data[2] 是一样的道理。\n\n## 下一篇文章\n\n[JavaScript 深入之参数按值传递](https://github.com/mqyqingfeng/Blog/issues/10)\n\n## 相关链接\n\n如果想了解执行上下文的具体变化，不妨循序渐进，阅读这六篇：\n\n- [《JavaScript 深入之词法作用域和动态作用域》](https://github.com/mqyqingfeng/Blog/issues/3)\n- [《JavaScript 深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)\n- [《JavaScript 深入之变量对象》](https://github.com/mqyqingfeng/Blog/issues/5)\n- [《JavaScript 深入之作用域链》](https://github.com/mqyqingfeng/Blog/issues/6)\n- [《JavaScript 深入之从 ECMAScript 规范解读 this》](https://github.com/mqyqingfeng/Blog/issues/7)\n- [《JavaScript 深入之执行上下文》](https://github.com/mqyqingfeng/Blog/issues/8)', '2019-02-11 14:00:44', '2019-02-11 14:00:44');
INSERT INTO `article` VALUES (55, '[转] JavaScript深入之参数按值传递', '> JavaScript 深入系列第九篇，除了按值传递、引用传递，还有第三种传递方式 —— 按共享传递\n\n## 定义\n\n在《JavaScript 高级程序设计》第三版 4.1.3，讲到传递参数：\n\n> ECMAScript 中所有函数的参数都是按值传递的。\n\n什么是按值传递呢？\n\n> 也就是说，把函数外部的值复制给函数内部的参数，就和把值从一个变量复制到另一个变量一样。\n\n<!--more-->\n\n## 按值传递\n\n举个简单的例子：\n\n```js\nvar value = 1\nfunction foo(v) {\n  v = 2\n  console.log(v) //2\n}\nfoo(value)\nconsole.log(value) // 1\n```\n\n很好理解，当传递 `value` 到函数 `foo` 中，相当于拷贝了一份 `value`，假设拷贝的这份叫 `\\_value`，函数中修改的都是 `\\_value` 的值，而不会影响原来的 `value` 值。\n\n## 引用传递\n\n拷贝虽然很好理解，但是当值是一个复杂的数据结构的时候，拷贝就会产生性能上的问题。\n\n所以还有另一种传递方式叫做按引用传递。\n\n所谓按引用传递，就是传递对象的引用，函数内部对参数的任何改变都会影响该对象的值，因为两者引用的是同一个对象。\n\n举个例子：\n\n```js\nvar obj = {\n  value: 1\n}\nfunction foo(o) {\n  o.value = 2\n  console.log(o.value) //2\n}\nfoo(obj)\nconsole.log(obj.value) // 2\n```\n\n哎，不对啊，连我们的红宝书都说了 ECMAScript 中所有函数的参数都是按值传递的，这怎么能按引用传递成功呢？\n\n而这究竟是不是引用传递呢？\n\n## 第三种传递方式\n\n不急，让我们再看个例子：\n\n```js\nvar obj = {\n  value: 1\n}\nfunction foo(o) {\n  o = 2\n  console.log(o) //2\n}\nfoo(obj)\nconsole.log(obj.value) // 1\n```\n\n如果 JavaScript 采用的是引用传递，外层的值也会被修改呐，这怎么又没被改呢？所以真的不是引用传递吗？\n\n这就要讲到其实还有第三种传递方式，叫按共享传递。\n\n而共享传递是指，在传递对象的时候，传递对象的引用的副本。\n\n**注意： 按引用传递是传递对象的引用，而按共享传递是传递对象的引用的副本！**\n\n所以修改 `o.value`，可以通过引用找到原值，但是直接修改 `o`，并不会修改原值。所以第二个和第三个例子其实都是按共享传递。\n\n最后，你可以这样理解：\n\n参数如果是基本类型是按值传递，如果是引用类型按共享传递。\n\n但是因为拷贝副本也是一种值的拷贝，所以在高程中也直接认为是按值传递了。\n\n所以，高程，谁叫你是红宝书嘞！\n\n## 下一篇文章\n\n[JavaScript 深入之 call 和 apply 的模拟实现](https://github.com/mqyqingfeng/Blog/issues/11)', '2019-02-11 14:02:08', '2019-02-11 14:02:08');
INSERT INTO `article` VALUES (56, '[转] JavaScript深入之call和apply的模拟实现', '## call\n\n一句话介绍 call：\n\n> call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。\n\n举个例子：\n\n```js\nvar foo = {\n  value: 1\n}\n\nfunction bar() {\n  console.log(this.value)\n}\n\nbar.call(foo) // 1\n```\n\n注意两点：\n\n1. call 改变了 this 的指向，指向到 `foo`\n2. `bar` 函数执行了\n\n<!--more-->\n\n## 模拟实现第一步\n\n那么我们该怎么模拟实现这两个效果呢？\n\n试想当调用 call 的时候，把 `foo` 对象改造成如下：\n\n```js\nvar foo = {\n  value: 1,\n  bar: function() {\n    console.log(this.value)\n  }\n}\n\nfoo.bar() // 1\n```\n\n这个时候 this 就指向了 `foo`，是不是很简单呢？\n\n但是这样却给 `foo` 对象本身添加了一个属性，这可不行呐！\n\n不过也不用担心，我们用 `delete` 再删除它不就好了~\n\n所以我们模拟的步骤可以分为：\n\n1. 将函数设为对象的属性\n2. 执行该函数\n3. 删除该函数\n\n以上个例子为例，就是：\n\n```js\n// 第一步\nfoo.fn = bar\n// 第二步\nfoo.fn()\n// 第三步\ndelete foo.fn\n```\n\n`fn` 是对象的属性名，反正最后也要删除它，所以起成什么都无所谓。\n\n根据这个思路，我们可以尝试着去写第一版的 **call2** 函数：\n\n```js\n// 第一版\nFunction.prototype.call2 = function(context) {\n  // 首先要获取调用call的函数，用this可以获取\n  context.fn = this\n  context.fn()\n  delete context.fn\n}\n\n// 测试一下\nvar foo = {\n  value: 1\n}\n\nfunction bar() {\n  console.log(this.value)\n}\n\nbar.call2(foo) // 1\n```\n\n正好可以打印 1 哎！是不是很开心！(～￣ ▽ ￣)～\n\n## 模拟实现第二步\n\n最一开始也讲了，call 函数还能给定参数执行函数。举个例子：\n\n```js\nvar foo = {\n  value: 1\n}\n\nfunction bar(name, age) {\n  console.log(name)\n  console.log(age)\n  console.log(this.value)\n}\n\nbar.call(foo, \'kevin\', 18)\n// kevin\n// 18\n// 1\n```\n\n注意：传入的参数并不确定，这可咋办？\n\n不急，我们可以从 Arguments 对象中取值，取出第二个到最后一个参数，然后放到一个数组里。\n\n比如这样：\n\n```js\n// 以上个例子为例，此时的arguments为：\n// arguments = {\n//      0: foo,\n//      1: \'kevin\',\n//      2: 18,\n//      length: 3\n// }\n// 因为arguments是类数组对象，所以可以用for循环\nvar args = []\nfor (var i = 1, len = arguments.length; i < len; i++) {\n  args.push(\'arguments[\' + i + \']\')\n}\n\n// 执行后 args为 [\"arguments[1]\", \"arguments[2]\", \"arguments[3]\"]\n```\n\n不定长的参数问题解决了，我们接着要把这个参数数组放到要执行的函数的参数里面去。\n\n```js\n// 将数组里的元素作为多个参数放进函数的形参里\ncontext.fn(args.join(\',\'))\n// (O_o)??\n// 这个方法肯定是不行的啦！！！\n```\n\n也许有人想到用 ES6 的方法，不过 call 是 ES3 的方法，我们为了模拟实现一个 ES3 的方法，要用到 ES6 的方法，好像……，嗯，也可以啦。但是我们这次用 eval 方法拼成一个函数，类似于这样：\n\n```js\neval(\'context.fn(\' + args + \')\')\n```\n\n这里 args 会自动调用 Array.toString() 这个方法。\n\n所以我们的第二版克服了两个大问题，代码如下：\n\n```js\n// 第二版\nFunction.prototype.call2 = function(context) {\n  context.fn = this\n  var args = []\n  for (var i = 1, len = arguments.length; i < len; i++) {\n    args.push(\'arguments[\' + i + \']\')\n  }\n  eval(\'context.fn(\' + args + \')\')\n  delete context.fn\n}\n\n// 测试一下\nvar foo = {\n  value: 1\n}\n\nfunction bar(name, age) {\n  console.log(name)\n  console.log(age)\n  console.log(this.value)\n}\n\nbar.call2(foo, \'kevin\', 18)\n// kevin\n// 18\n// 1\n```\n\n(๑•̀ㅂ•́)و✧\n\n## 模拟实现第三步\n\n模拟代码已经完成 80%，还有两个小点要注意：\n\n**1.this 参数可以传 null，当为 null 的时候，视为指向 window**\n\n举个例子：\n\n```js\nvar value = 1\n\nfunction bar() {\n  console.log(this.value)\n}\n\nbar.call(null) // 1\n```\n\n虽然这个例子本身不使用 call，结果依然一样。\n\n**2.函数是可以有返回值的！**\n\n举个例子：\n\n```js\nvar obj = {\n  value: 1\n}\n\nfunction bar(name, age) {\n  return {\n    value: this.value,\n    name: name,\n    age: age\n  }\n}\n\nconsole.log(bar.call(obj, \'kevin\', 18))\n// Object {\n//    value: 1,\n//    name: \'kevin\',\n//    age: 18\n// }\n```\n\n不过都很好解决，让我们直接看第三版也就是最后一版的代码：\n\n```js\n// 第三版\nFunction.prototype.call2 = function(context) {\n  var context = context || window\n  context.fn = this\n\n  var args = []\n  for (var i = 1, len = arguments.length; i < len; i++) {\n    args.push(\'arguments[\' + i + \']\')\n  }\n\n  var result = eval(\'context.fn(\' + args + \')\')\n\n  delete context.fn\n  return result\n}\n\n// 测试一下\nvar value = 2\n\nvar obj = {\n  value: 1\n}\n\nfunction bar(name, age) {\n  console.log(this.value)\n  return {\n    value: this.value,\n    name: name,\n    age: age\n  }\n}\n\nbar.call(null) // 2\n\nconsole.log(bar.call2(obj, \'kevin\', 18))\n// 1\n// Object {\n//    value: 1,\n//    name: \'kevin\',\n//    age: 18\n// }\n```\n\n到此，我们完成了 call 的模拟实现，给自己一个赞 ｂ（￣ ▽ ￣）ｄ\n\n## apply 的模拟实现\n\napply 的实现跟 call 类似，在这里直接给代码，代码来自于知乎 @郑航的实现：\n\n```js\nFunction.prototype.apply = function(context, arr) {\n  var context = Object(context) || window\n  context.fn = this\n\n  var result\n  if (!arr) {\n    result = context.fn()\n  } else {\n    var args = []\n    for (var i = 0, len = arr.length; i < len; i++) {\n      args.push(\'arr[\' + i + \']\')\n    }\n    result = eval(\'context.fn(\' + args + \')\')\n  }\n\n  delete context.fn\n  return result\n}\n```\n\n## 下一篇文章\n\n[JavaScript 深入之 bind 的模拟实现](https://github.com/mqyqingfeng/Blog/issues/12)\n\n## 重要参考\n\n[知乎问题 不能使用 call、apply、bind，如何用 js 实现 call 或者 apply 的功能？](https://www.zhihu.com/question/35787390)\n\n## 深入系列\n\nJavaScript 深入系列目录地址：[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。\n\nJavaScript 深入系列预计写十五篇左右，旨在帮大家捋顺 JavaScript 底层知识，重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。\n\n如果有错误或者不严谨的地方，请务必给予指正，十分感谢。如果喜欢或者有所启发，欢迎 star，对作者也是一种鼓励。', '2019-02-11 14:02:40', '2019-02-11 14:02:40');
INSERT INTO `article` VALUES (57, '[转] Javascript深入之new的模拟实现', '## new\n\n一句话介绍 new:\n\n> new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一\n\n也许有点难懂，我们在模拟 new 之前，先看看 new 实现了哪些功能。\n\n举个例子：\n\n```js\n// Otaku 御宅族，简称宅\nfunction Otaku(name, age) {\n  this.name = name\n  this.age = age\n\n  this.habit = \'Games\'\n}\n\n// 因为缺乏锻炼的缘故，身体强度让人担忧\nOtaku.prototype.strength = 60\n\nOtaku.prototype.sayYourName = function() {\n  console.log(\'I am \' + this.name)\n}\n\nvar person = new Otaku(\'Kevin\', \'18\')\n\nconsole.log(person.name) // Kevin\nconsole.log(person.habit) // Games\nconsole.log(person.strength) // 60\n\nperson.sayYourName() // I am Kevin\n```\n\n<!--more-->\n\n从这个例子中，我们可以看到，实例 person 可以：\n\n1. 访问到 Otaku 构造函数里的属性\n2. 访问到 Otaku.prototype 中的属性\n\n接下来，我们可以尝试着模拟一下了。\n\n因为 new 是关键字，所以无法像 bind 函数一样直接覆盖，所以我们写一个函数，命名为 objectFactory，来模拟 new 的效果。用的时候是这样的：\n\n```js\nfunction Otaku () {\n    ……\n}\n\n// 使用 new\nvar person = new Otaku(……);\n// 使用 objectFactory\nvar person = objectFactory(Otaku, ……)\n```\n\n## 初步实现\n\n分析：\n\n因为 new 的结果是一个新对象，所以在模拟实现的时候，我们也要建立一个新对象，假设这个对象叫 obj，因为 obj 会具有 Otaku 构造函数里的属性，想想经典继承的例子，我们可以使用 Otaku.apply(obj, arguments)来给 obj 添加新的属性。\n\n在 JavaScript 深入系列第一篇中，我们便讲了原型与原型链，我们知道实例的 \\_\\_proto\\_\\_ 属性会指向构造函数的 prototype，也正是因为建立起这样的关系，实例可以访问原型上的属性。\n\n现在，我们可以尝试着写第一版了：\n\n```js\n// 第一版代码\nfunction objectFactory() {\n  var obj = new Object(),\n    Constructor = [].shift.call(arguments)\n\n  obj.__proto__ = Constructor.prototype\n\n  Constructor.apply(obj, arguments)\n\n  return obj\n}\n```\n\n在这一版中，我们：\n\n1. 用 new Object() 的方式新建了一个对象 obj\n2. 取出第一个参数，就是我们要传入的构造函数。此外因为 shift 会修改原数组，所以 arguments 会被去除第一个参数\n3. 将 obj 的原型指向构造函数，这样 obj 就可以访问到构造函数原型中的属性\n4. 使用 apply，改变构造函数 this 的指向到新建的对象，这样 obj 就可以访问到构造函数中的属性\n5. 返回 obj\n\n更多关于：\n\n原型与原型链，可以看[《JavaScript 深入之从原型到原型链》](https://github.com/mqyqingfeng/Blog/issues/2)\n\napply，可以看[《JavaScript 深入之 call 和 apply 的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/11)\n\n经典继承，可以看[《JavaScript 深入之继承》](https://github.com/mqyqingfeng/Blog/issues/16)\n\n复制以下的代码，到浏览器中，我们可以做一下测试：\n\n```js\nfunction Otaku(name, age) {\n  this.name = name\n  this.age = age\n\n  this.habit = \'Games\'\n}\n\nOtaku.prototype.strength = 60\n\nOtaku.prototype.sayYourName = function() {\n  console.log(\'I am \' + this.name)\n}\n\nfunction objectFactory() {\n  var obj = new Object(),\n    Constructor = [].shift.call(arguments)\n  obj.__proto__ = Constructor.prototype\n  Constructor.apply(obj, arguments)\n  return obj\n}\n\nvar person = objectFactory(Otaku, \'Kevin\', \'18\')\n\nconsole.log(person.name) // Kevin\nconsole.log(person.habit) // Games\nconsole.log(person.strength) // 60\n\nperson.sayYourName() // I am Kevin\n```\n\n[]\\~(￣ ▽ ￣)\\~\\*\\*\n\n## 返回值效果实现\n\n接下来我们再来看一种情况，假如构造函数有返回值，举个例子：\n\n```js\nfunction Otaku(name, age) {\n  this.strength = 60\n  this.age = age\n\n  return {\n    name: name,\n    habit: \'Games\'\n  }\n}\n\nvar person = new Otaku(\'Kevin\', \'18\')\n\nconsole.log(person.name) // Kevin\nconsole.log(person.habit) // Games\nconsole.log(person.strength) // undefined\nconsole.log(person.age) // undefined\n```\n\n在这个例子中，构造函数返回了一个对象，在实例 person 中只能访问返回的对象中的属性。\n\n而且还要注意一点，在这里我们是返回了一个对象，假如我们只是返回一个基本类型的值呢？\n\n再举个例子：\n\n```js\nfunction Otaku(name, age) {\n  this.strength = 60\n  this.age = age\n\n  return \'handsome boy\'\n}\n\nvar person = new Otaku(\'Kevin\', \'18\')\n\nconsole.log(person.name) // undefined\nconsole.log(person.habit) // undefined\nconsole.log(person.strength) // 60\nconsole.log(person.age) // 18\n```\n\n结果完全颠倒过来，这次尽管有返回值，但是相当于没有返回值进行处理。\n\n所以我们还需要判断返回的值是不是一个对象，如果是一个对象，我们就返回这个对象，如果没有，我们该返回什么就返回什么。\n\n再来看第二版的代码，也是最后一版的代码：\n\n```js\n// 第二版的代码\nfunction objectFactory() {\n  var obj = new Object(),\n    Constructor = [].shift.call(arguments)\n\n  obj.__proto__ = Constructor.prototype\n\n  var ret = Constructor.apply(obj, arguments)\n\n  return typeof ret === \'object\' ? ret : obj\n}\n```\n\n## 下一篇文章\n\n[JavaScript 深入之类数组对象与 arguments](https://github.com/mqyqingfeng/Blog/issues/14)\n\n## 相关链接\n\n- [《JavaScript 深入之从原型到原型链》](https://github.com/mqyqingfeng/Blog/issues/2)\n- [《JavaScript 深入之 call 和 apply 的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/11)\n- [《JavaScript 深入之继承》](https://github.com/mqyqingfeng/Blog/issues/16)\n\n## 深入系列\n\nJavaScript 深入系列目录地址：[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。\n\nJavaScript 深入系列预计写十五篇左右，旨在帮大家捋顺 JavaScript 底层知识，重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。\n\n如果有错误或者不严谨的地方，请务必给予指正，十分感谢。如果喜欢或者有所启发，欢迎 star，对作者也是一种鼓励。', '2019-02-11 14:03:13', '2019-02-11 14:03:13');
INSERT INTO `article` VALUES (58, '[转]  JavaScript深入之类数组对象与arguments', '## 类数组对象\n\n所谓的类数组对象:\n\n> 拥有一个 length 属性和若干索引属性的对象\n\n举个例子：\n\n```js\nvar array = [\'name\', \'age\', \'sex\']\n\nvar arrayLike = {\n  0: \'name\',\n  1: \'age\',\n  2: \'sex\',\n  length: 3\n}\n```\n\n即便如此，为什么叫做类数组对象呢？\n\n那让我们从读写、获取长度、遍历三个方面看看这两个对象。\n\n## 读写\n\n```js\nconsole.log(array[0]) // name\nconsole.log(arrayLike[0]) // name\n\narray[0] = \'new name\'\narrayLike[0] = \'new name\'\n```\n<!--more-->\n\n## 长度\n\n```js\nconsole.log(array.length) // 3\nconsole.log(arrayLike.length) // 3\n```\n\n## 遍历\n\n```js\nfor(var i = 0, len = array.length; i < len; i++) {\n   ……\n}\nfor(var i = 0, len = arrayLike.length; i < len; i++) {\n    ……\n}\n```\n\n是不是很像？\n\n那类数组对象可以使用数组的方法吗？比如：\n\n```js\narrayLike.push(\'4\')\n```\n\n然而上述代码会报错: arrayLike.push is not a function\n\n所以终归还是类数组呐……\n\n## 调用数组方法\n\n如果类数组就是任性的想用数组的方法怎么办呢？\n\n既然无法直接调用，我们可以用 Function.call 间接调用：\n\n```js\nvar arrayLike = { 0: \'name\', 1: \'age\', 2: \'sex\', length: 3 }\n\nArray.prototype.join.call(arrayLike, \'&\') // name&age&sex\n\nArray.prototype.slice.call(arrayLike, 0) // [\"name\", \"age\", \"sex\"]\n// slice可以做到类数组转数组\n\nArray.prototype.map.call(arrayLike, function(item) {\n  return item.toUpperCase()\n})\n// [\"NAME\", \"AGE\", \"SEX\"]\n```\n\n## 类数组转对象\n\n在上面的例子中已经提到了一种类数组转数组的方法，再补充三个：\n\n```js\nvar arrayLike = { 0: \'name\', 1: \'age\', 2: \'sex\', length: 3 }\n// 1. slice\nArray.prototype.slice.call(arrayLike) // [\"name\", \"age\", \"sex\"]\n// 2. splice\nArray.prototype.splice.call(arrayLike, 0) // [\"name\", \"age\", \"sex\"]\n// 3. ES6 Array.from\nArray.from(arrayLike) // [\"name\", \"age\", \"sex\"]\n// 4. apply\nArray.prototype.concat.apply([], arrayLike)\n```\n\n那么为什么会讲到类数组对象呢？以及类数组有什么应用吗？\n\n要说到类数组对象，Arguments 对象就是一个类数组对象。在客户端 JavaScript 中，一些 DOM 方法(document.getElementsByTagName()等)也返回类数组对象。\n\n## Arguments 对象\n\n接下来重点讲讲 Arguments 对象。\n\nArguments 对象只定义在函数体中，包括了函数的参数和其他属性。在函数体中，arguments 指代该函数的 Arguments 对象。\n\n举个例子：\n\n```js\nfunction foo(name, age, sex) {\n  console.log(arguments)\n}\n\nfoo(\'name\', \'age\', \'sex\')\n```\n\n打印结果如下：\n\n![arguments](https://github.com/mqyqingfeng/Blog/raw/master/Images/arguments.png)\n\n我们可以看到除了类数组的索引属性和 length 属性之外，还有一个 callee 属性，接下来我们一个一个介绍。\n\n## length 属性\n\nArguments 对象的 length 属性，表示实参的长度，举个例子：\n\n```js\nfunction foo(b, c, d) {\n  console.log(\'实参的长度为：\' + arguments.length)\n}\n\nconsole.log(\'形参的长度为：\' + foo.length)\n\nfoo(1)\n\n// 形参的长度为：3\n// 实参的长度为：1\n```\n\n## callee 属性\n\nArguments 对象的 callee 属性，通过它可以调用函数自身。\n\n讲个闭包经典面试题使用 callee 的解决方法：\n\n```js\nvar data = []\n\nfor (var i = 0; i < 3; i++) {\n  ;(data[i] = function() {\n    console.log(arguments.callee.i)\n  }).i = i\n}\n\ndata[0]()\ndata[1]()\ndata[2]()\n\n// 0\n// 1\n// 2\n```\n\n接下来讲讲 arguments 对象的几个注意要点：\n\n## arguments 和对应参数的绑定\n\n```js\nfunction foo(name, age, sex, hobbit) {\n  console.log(name, arguments[0]) // name name\n\n  // 改变形参\n  name = \'new name\'\n\n  console.log(name, arguments[0]) // new name new name\n\n  // 改变arguments\n  arguments[1] = \'new age\'\n\n  console.log(age, arguments[1]) // new age new age\n\n  // 测试未传入的是否会绑定\n  console.log(sex) // undefined\n\n  sex = \'new sex\'\n\n  console.log(sex, arguments[2]) // new sex undefined\n\n  arguments[3] = \'new hobbit\'\n\n  console.log(hobbit, arguments[3]) // undefined new hobbit\n}\n\nfoo(\'name\', \'age\')\n```\n\n传入的参数，实参和 arguments 的值会共享，当没有传入时，实参与 arguments 值不会共享\n\n除此之外，以上是在非严格模式下，如果是在严格模式下，实参和 arguments 是不会共享的。\n\n## 传递参数\n\n将参数从一个函数传递到另一个函数\n\n```js\n// 使用 apply 将 foo 的参数传递给 bar\nfunction foo() {\n  bar.apply(this, arguments)\n}\nfunction bar(a, b, c) {\n  console.log(a, b, c)\n}\n\nfoo(1, 2, 3)\n```\n\n## 强大的 ES6\n\n使用 ES6 的 ... 运算符，我们可以轻松转成数组。\n\n```js\nfunction func(...arguments) {\n  console.log(arguments) // [1, 2, 3]\n}\n\nfunc(1, 2, 3)\n```\n\n## 应用\n\narguments 的应用其实很多，在下个系列，也就是 JavaScript 专题系列中，我们会在 jQuery 的 extend 实现、函数柯里化、递归等场景看见 arguments 的身影。这篇文章就不具体展开了。\n\n如果要总结这些场景的话，暂时能想到的包括：\n\n1. 参数不定长\n2. 函数柯里化\n3. 递归调用\n4. 函数重载\n   ...\n\n欢迎留言回复。\n\n## 下一篇文章\n\n[JavaScript 深入之创建对象的多种方式以及优缺点](https://github.com/mqyqingfeng/Blog/issues/15)\n\n## 深入系列\n\nJavaScript 深入系列目录地址：[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。\n\nJavaScript 深入系列预计写十五篇左右，旨在帮大家捋顺 JavaScript 底层知识，重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。\n\n如果有错误或者不严谨的地方，请务必给予指正，十分感谢。如果喜欢或者有所启发，欢迎 star，对作者也是一种鼓励。', '2019-02-11 14:03:52', '2019-02-11 14:03:52');
INSERT INTO `article` VALUES (59, '[转] JavaScript深入之创建对象的多种方式以及优缺点', '## 写在前面\n\n这篇文章讲解创建对象的各种方式，以及优缺点。\n\n但是注意：\n\n这篇文章更像是笔记，因为《JavaScript 高级程序设计》写得真是太好了！\n\n## 1. 工厂模式\n\n```js\nfunction createPerson(name) {\n  var o = new Object()\n  o.name = name\n  o.getName = function() {\n    console.log(this.name)\n  }\n\n  return o\n}\n\nvar person1 = createPerson(\'kevin\')\n```\n\n缺点：对象无法识别，因为所有的实例都指向一个原型\n\n## 2. 构造函数模式\n\n```js\nfunction Person(name) {\n  this.name = name\n  this.getName = function() {\n    console.log(this.name)\n  }\n}\n\nvar person1 = new Person(\'kevin\')\n```\n\n优点：实例可以识别为一个特定的类型\n\n缺点：每次创建实例时，每个方法都要被创建一次\n\n<!--more-->\n\n## 2.1 构造函数模式优化\n\n```js\nfunction Person(name) {\n  this.name = name\n  this.getName = getName\n}\n\nfunction getName() {\n  console.log(this.name)\n}\n\nvar person1 = new Person(\'kevin\')\n```\n\n优点：解决了每个方法都要被重新创建的问题\n\n缺点：这叫啥封装……\n\n## 3. 原型模式\n\n```js\nfunction Person(name) {}\n\nPerson.prototype.name = \'keivn\'\nPerson.prototype.getName = function() {\n  console.log(this.name)\n}\n\nvar person1 = new Person()\n```\n\n优点：方法不会重新创建\n\n缺点：1. 所有的属性和方法都共享 2. 不能初始化参数\n\n## 3.1 原型模式优化\n\n```js\nfunction Person(name) {}\n\nPerson.prototype = {\n  name: \'kevin\',\n  getName: function() {\n    console.log(this.name)\n  }\n}\n\nvar person1 = new Person()\n```\n\n优点：封装性好了一点\n\n缺点：重写了原型，丢失了 constructor 属性\n\n## 3.2 原型模式优化\n\n```js\nfunction Person(name) {}\n\nPerson.prototype = {\n  constructor: Person,\n  name: \'kevin\',\n  getName: function() {\n    console.log(this.name)\n  }\n}\n\nvar person1 = new Person()\n```\n\n优点：实例可以通过 constructor 属性找到所属构造函数\n\n缺点：原型模式该有的缺点还是有\n\n## 4. 组合模式\n\n构造函数模式与原型模式双剑合璧。\n\n```js\nfunction Person(name) {\n  this.name = name\n}\n\nPerson.prototype = {\n  constructor: Person,\n  getName: function() {\n    console.log(this.name)\n  }\n}\n\nvar person1 = new Person()\n```\n\n优点：该共享的共享，该私有的私有，使用最广泛的方式\n\n缺点：有的人就是希望全部都写在一起，即更好的封装性\n\n## 4.1 动态原型模式\n\n```js\nfunction Person(name) {\n  this.name = name\n  if (typeof this.getName != \'function\') {\n    Person.prototype.getName = function() {\n      console.log(this.name)\n    }\n  }\n}\n\nvar person1 = new Person()\n```\n\n注意：使用动态原型模式时，不能用对象字面量重写原型\n\n解释下为什么：\n\n```js\nfunction Person(name) {\n  this.name = name\n  if (typeof this.getName != \'function\') {\n    Person.prototype = {\n      constructor: Person,\n      getName: function() {\n        console.log(this.name)\n      }\n    }\n  }\n}\n\nvar person1 = new Person(\'kevin\')\nvar person2 = new Person(\'daisy\')\n\n// 报错 并没有该方法\nperson1.getName()\n\n// 注释掉上面的代码，这句是可以执行的。\nperson2.getName()\n```\n\n为了解释这个问题，假设开始执行`var person1 = new Person(\'kevin\')`。\n\n如果对 new 和 apply 的底层执行过程不是很熟悉，可以阅读底部相关链接中的文章。\n\n我们回顾下 new 的实现步骤：\n\n1. 首先新建一个对象\n2. 然后将对象的原型指向 Person.prototype\n3. 然后 Person.apply(obj)\n4. 返回这个对象\n\n注意这个时候，回顾下 apply 的实现步骤，会执行 obj.Person 方法，这个时候就会执行 if 语句里的内容，注意构造函数的 prototype 属性指向了实例的原型，使用字面量方式直接覆盖 Person.prototype，并不会更改实例的原型的值，person1 依然是指向了以前的原型，而不是 Person.prototype。而之前的原型是没有 getName 方法的，所以就报错了！\n\n如果你就是想用字面量方式写代码，可以尝试下这种：\n\n```js\nfunction Person(name) {\n  this.name = name\n  if (typeof this.getName != \'function\') {\n    Person.prototype = {\n      constructor: Person,\n      getName: function() {\n        console.log(this.name)\n      }\n    }\n\n    return new Person(name)\n  }\n}\n\nvar person1 = new Person(\'kevin\')\nvar person2 = new Person(\'daisy\')\n\nperson1.getName() // kevin\nperson2.getName() // daisy\n```\n\n### 5.1 寄生构造函数模式\n\n```js\nfunction Person(name) {\n  var o = new Object()\n  o.name = name\n  o.getName = function() {\n    console.log(this.name)\n  }\n\n  return o\n}\n\nvar person1 = new Person(\'kevin\')\nconsole.log(person1 instanceof Person) // false\nconsole.log(person1 instanceof Object) // true\n```\n\n寄生构造函数模式，我个人认为应该这样读：\n\n寄生-构造函数-模式，也就是说寄生在构造函数的一种方法。\n\n也就是说打着构造函数的幌子挂羊头卖狗肉，你看创建的实例使用 instanceof 都无法指向构造函数！\n\n这样方法可以在特殊情况下使用。比如我们想创建一个具有额外方法的特殊数组，但是又不想直接修改 Array 构造函数，我们可以这样写：\n\n```js\nfunction SpecialArray() {\n  var values = new Array()\n\n  for (var i = 0, len = arguments.length; i < len; i++) {\n    values.push(arguments[i])\n  }\n\n  values.toPipedString = function() {\n    return this.join(\'|\')\n  }\n  return values\n}\n\nvar colors = new SpecialArray(\'red\', \'blue\', \'green\')\nvar colors2 = SpecialArray(\'red2\', \'blue2\', \'green2\')\n\nconsole.log(colors)\nconsole.log(colors.toPipedString()) // red|blue|green\n\nconsole.log(colors2)\nconsole.log(colors2.toPipedString()) // red2|blue2|green2\n```\n\n你会发现，其实所谓的寄生构造函数模式就是比工厂模式在创建对象的时候，多使用了一个 new，实际上两者的结果是一样的。\n\n但是作者可能是希望能像使用普通 Array 一样使用 SpecialArray，虽然把 SpecialArray 当成函数也一样能用，但是这并不是作者的本意，也变得不优雅。\n\n在可以使用其他模式的情况下，不要使用这种模式。\n\n但是值得一提的是，上面例子中的循环：\n\n```js\nfor (var i = 0, len = arguments.length; i < len; i++) {\n  values.push(arguments[i])\n}\n```\n\n可以替换成：\n\n```js\nvalues.push.apply(values, arguments)\n```\n\n## 5.2 稳妥构造函数模式\n\n```js\nfunction person(name) {\n  var o = new Object()\n  o.sayName = function() {\n    console.log(name)\n  }\n  return o\n}\n\nvar person1 = person(\'kevin\')\n\nperson1.sayName() // kevin\n\nperson1.name = \'daisy\'\n\nperson1.sayName() // kevin\n\nconsole.log(person1.name) // daisy\n```\n\n所谓稳妥对象，指的是没有公共属性，而且其方法也不引用 this 的对象。\n\n与寄生构造函数模式有两点不同：\n\n1. 新创建的实例方法不引用 this\n2. 不使用 new 操作符调用构造函数\n\n稳妥对象最适合在一些安全的环境中。\n\n稳妥构造函数模式也跟工厂模式一样，无法识别对象所属类型。\n\n## 下一篇文章\n\n[JavaScript 深入之继承的多种方式和优缺点](https://github.com/mqyqingfeng/Blog/issues/16)\n\n## 相关链接\n\n- [《JavaScript 深入之从原型到原型链》](https://github.com/mqyqingfeng/Blog/issues/2)\n- [《JavaScript 深入之 new 的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/13)\n- [《JavaScript 深入之 call 和 apply 的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/11)\n\n## 深入系列\n\nJavaScript 深入系列目录地址：[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。\n\nJavaScript 深入系列预计写十五篇左右，旨在帮大家捋顺 JavaScript 底层知识，重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。\n\n如果有错误或者不严谨的地方，请务必给予指正，十分感谢。如果喜欢或者有所启发，欢迎 star，对作者也是一种鼓励。', '2019-02-11 14:04:15', '2019-02-11 14:04:15');
INSERT INTO `article` VALUES (60, '[转] JavaScript深入之继承的多种方式和优缺点', '## 写在前面\n\n本文讲解 JavaScript 各种继承方式和优缺点。\n\n但是注意：\n\n这篇文章更像是笔记，哎，再让我感叹一句：《JavaScript 高级程序设计》写得真是太好了！\n\n## 1.原型链继承\n\n```js\nfunction Parent() {\n  this.name = \'kevin\'\n}\n\nParent.prototype.getName = function() {\n  console.log(this.name)\n}\n\nfunction Child() {}\n\nChild.prototype = new Parent()\n\nvar child1 = new Child()\n\nconsole.log(child1.getName()) // kevin\n```\n\n问题：\n\n1.引用类型的属性被所有实例共享，举个例子：\n\n```js\nfunction Parent() {\n  this.names = [\'kevin\', \'daisy\']\n}\n\nfunction Child() {}\n\nChild.prototype = new Parent()\n\nvar child1 = new Child()\n\nchild1.names.push(\'yayu\')\n\nconsole.log(child1.names) // [\"kevin\", \"daisy\", \"yayu\"]\n\nvar child2 = new Child()\n\nconsole.log(child2.names) // [\"kevin\", \"daisy\", \"yayu\"]\n```\n\n2.在创建 Child 的实例时，不能向 Parent 传参\n\n<!--more-->\n\n## 2.借用构造函数(经典继承)\n\n```js\nfunction Parent() {\n  this.names = [\'kevin\', \'daisy\']\n}\n\nfunction Child() {\n  Parent.call(this)\n}\n\nvar child1 = new Child()\n\nchild1.names.push(\'yayu\')\n\nconsole.log(child1.names) // [\"kevin\", \"daisy\", \"yayu\"]\n\nvar child2 = new Child()\n\nconsole.log(child2.names) // [\"kevin\", \"daisy\"]\n```\n\n优点：\n\n1.避免了引用类型的属性被所有实例共享\n\n2.可以在 Child 中向 Parent 传参\n\n举个例子：\n\n```js\nfunction Parent(name) {\n  this.name = name\n}\n\nfunction Child(name) {\n  Parent.call(this, name)\n}\n\nvar child1 = new Child(\'kevin\')\n\nconsole.log(child1.name) // kevin\n\nvar child2 = new Child(\'daisy\')\n\nconsole.log(child2.name) // daisy\n```\n\n缺点：\n\n方法都在构造函数中定义，每次创建实例都会创建一遍方法。\n\n## 3.组合继承\n\n原型链继承和经典继承双剑合璧。\n\n```js\nfunction Parent(name) {\n  this.name = name\n  this.colors = [\'red\', \'blue\', \'green\']\n}\n\nParent.prototype.getName = function() {\n  console.log(this.name)\n}\n\nfunction Child(name, age) {\n  Parent.call(this, name)\n\n  this.age = age\n}\n\nChild.prototype = new Parent()\n\nvar child1 = new Child(\'kevin\', \'18\')\n\nchild1.colors.push(\'black\')\n\nconsole.log(child1.name) // kevin\nconsole.log(child1.age) // 18\nconsole.log(child1.colors) // [\"red\", \"blue\", \"green\", \"black\"]\n\nvar child2 = new Child(\'daisy\', \'20\')\n\nconsole.log(child2.name) // daisy\nconsole.log(child2.age) // 20\nconsole.log(child2.colors) // [\"red\", \"blue\", \"green\"]\n```\n\n优点：融合原型链继承和构造函数的优点，是 JavaScript 中最常用的继承模式。\n\n## 4.原型式继承\n\n```js\nfunction createObj(o) {\n  function F() {}\n  F.prototype = o\n  return new F()\n}\n```\n\n就是 ES5 Object.create 的模拟实现，将传入的对象作为创建的对象的原型。\n\n缺点：\n\n包含引用类型的属性值始终都会共享相应的值，这点跟原型链继承一样。\n\n```js\nvar person = {\n  name: \'kevin\',\n  friends: [\'daisy\', \'kelly\']\n}\n\nvar person1 = createObj(person)\nvar person2 = createObj(person)\n\nperson1.name = \'person1\'\nconsole.log(person2.name) // kevin\n\nperson1.firends.push(\'taylor\')\nconsole.log(person2.friends) // [\"daisy\", \"kelly\", \"taylor\"]\n```\n\n注意：修改`person1.name`的值，`person2.name`的值并未发生改变，并不是因为`person1`和`person2`有独立的 name 值，而是因为`person1.name = \'person1\'`，给`person1`添加了 name 值，并非修改了原型上的 name 值。\n\n## 5. 寄生式继承\n\n创建一个仅用于封装继承过程的函数，该函数在内部以某种形式来做增强对象，最后返回对象。\n\n```js\nfunction createObj(o) {\n  var clone = object.create(o)\n  clone.sayName = function() {\n    console.log(\'hi\')\n  }\n  return clone\n}\n```\n\n缺点：跟借用构造函数模式一样，每次创建对象都会创建一遍方法。\n\n## 6. 寄生组合式继承\n\n为了方便大家阅读，在这里重复一下组合继承的代码：\n\n```js\nfunction Parent(name) {\n  this.name = name\n  this.colors = [\'red\', \'blue\', \'green\']\n}\n\nParent.prototype.getName = function() {\n  console.log(this.name)\n}\n\nfunction Child(name, age) {\n  Parent.call(this, name)\n  this.age = age\n}\n\nChild.prototype = new Parent()\n\nvar child1 = new Child(\'kevin\', \'18\')\n\nconsole.log(child1)\n```\n\n组合继承最大的缺点是会调用两次父构造函数。\n\n一次是设置子类型实例的原型的时候：\n\n```js\nChild.prototype = new Parent()\n```\n\n一次在创建子类型实例的时候：\n\n```js\nvar child1 = new Child(\'kevin\', \'18\')\n```\n\n回想下 new 的模拟实现，其实在这句中，我们会执行：\n\n```js\nParent.call(this, name)\n```\n\n在这里，我们又会调用了一次 Parent 构造函数。\n\n所以，在这个例子中，如果我们打印 child1 对象，我们会发现 Child.prototype 和 child1 都有一个属性为`colors`，属性值为`[\'red\', \'blue\', \'green\']`。\n\n那么我们该如何精益求精，避免这一次重复调用呢？\n\n如果我们不使用 Child.prototype = new Parent() ，而是间接的让 Child.prototype 访问到 Parent.prototype 呢？\n\n看看如何实现：\n\n```js\nfunction Parent(name) {\n  this.name = name\n  this.colors = [\'red\', \'blue\', \'green\']\n}\n\nParent.prototype.getName = function() {\n  console.log(this.name)\n}\n\nfunction Child(name, age) {\n  Parent.call(this, name)\n  this.age = age\n}\n\n// 关键的三步\nvar F = function() {}\n\nF.prototype = Parent.prototype\n\nChild.prototype = new F()\n\nvar child1 = new Child(\'kevin\', \'18\')\n\nconsole.log(child1)\n```\n\n最后我们封装一下这个继承方法：\n\n```js\nfunction object(o) {\n  function F() {}\n  F.prototype = o\n  return new F()\n}\n\nfunction prototype(child, parent) {\n  var prototype = object(parent.prototype)\n  prototype.constructor = child\n  child.prototype = prototype\n}\n\n// 当我们使用的时候：\nprototype(Child, Parent)\n```\n\n引用《JavaScript 高级程序设计》中对寄生组合式继承的夸赞就是：\n\n这种方式的高效率体现它只调用了一次 Parent 构造函数，并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时，原型链还能保持不变；因此，还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。\n\n## 相关链接\n\n- [《JavaScript 深入之从原型到原型链》](https://github.com/mqyqingfeng/Blog/issues/2)\n- [《JavaScript 深入之 call 和 apply 的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/11)\n- [《JavaScript 深入之 new 的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/13)\n- [《JavaScript 深入之创建对象》](https://github.com/mqyqingfeng/Blog/issues/15)\n\n## 深入系列\n\nJavaScript 深入系列目录地址：[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。\n\nJavaScript 深入系列预计写十五篇左右，旨在帮大家捋顺 JavaScript 底层知识，重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。\n\n如果有错误或者不严谨的地方，请务必给予指正，十分感谢。如果喜欢或者有所启发，欢迎 star，对作者也是一种鼓励。', '2019-02-11 14:04:50', '2019-02-11 14:04:50');
INSERT INTO `article` VALUES (61, 'flex 布局', '## Flex 基本概念\n![](http://pbj98r3fm.bkt.clouddn.com/flex.jpg)\n\n## Flex 容器相关属性\n```css\n.container{\n    display: flex | inline-flex;       //可以有两种取值\n}\n```\n> 需要注意的是：当时设置 flex 布局之后，子元素的 float、clear、vertical-align 的属性将会失效。\n有下面六种属性可以设置在容器上，它们分别是：\n\n> 1. `flex-direction`  \n>    1. 功能：决定水平方向的排列方向 \n>    2. 参数：row \\| row-reverse \\| column \\| column-reverse\n> 2. `flex-wrap`\n>    1. 功能：决定容器内项目是否可以换行\n>    2. 参数：nowrap \\| wrap \\| wrap-reverse\n> 3. flex-flow\n>    1. 功能：flex-direction、flex-wrap 的简写形式 ---&gt; 没什么卵用\n>    2. 参数：&lt;flex-direction&gt; \\| &lt;flex-wrap&gt;\n> 4. `justify-content`\n>    1. 功能：定义在水平方向的对齐方式\n>    2. 参数：flex-start \\| flex-end \\| center \\| space-between \\| space-around\n> 5. `align-items`\n>    1. 功能：定义了容器内项目的对齐方式\n>    2. 参数：stretch \\| flex-start \\| flex-end \\| center \\| baseline \n> 6. align-content\n>    1. 功能：定义了多根轴线的对齐方式，如果项目只有一根轴线，则该属性将不起作用\n>    2. 参数：stretch \\| flex-start \\| flex-end \\| center \\| space-between \\| space-around\n\n说明 ：参数首位为默认值 ；参数中含有 `reverse` 的表示反方向\n\n<!--more-->\n\n### Justify-content\n#### space-between 两端对齐\n    两端对齐，项目之间的间隔相等，即剩余空间等分成间隙\n![](http://pbj98r3fm.bkt.clouddn.com/space-between.png)\n#### space-around 项目之间间隔相等\n    每个项目两侧的间隔相等，所以项目之间的间隔比项目与边缘的间隔大一倍。\n![](http://pbj98r3fm.bkt.clouddn.com/space-around.png)\n\n### align-items\n建立在主轴为水平方向时测试，即 flex-direction: row ( 默认值 )\n\n#### stretch 默认值 占满整个容器的高度\n    即如果项目未设置高度或者设为 auto，将占满整个容器的高度。\n假设容器高度设置为 100px，而项目都没有设置高度的情况下，则项目的高度也为 100px。\n![](http://pbj98r3fm.bkt.clouddn.com/align-items01.jpg)   \n\n#### flex-start 交叉轴的起点对齐\n假设容器高度设置为 100px，而项目分别为 20px, 40px, 60px, 80px, 100px, 则如下图显示\n![](http://pbj98r3fm.bkt.clouddn.com/align-items02.jpg)  \n\n#### flex-end 交叉轴的终点对齐\n![](http://pbj98r3fm.bkt.clouddn.com/align-items03.jpg)\n\n#### center 交叉轴的中点对齐\n![](http://pbj98r3fm.bkt.clouddn.com/align-items04.jpg)\n\n#### baseline 项目的第一行文字的基线对齐\n![](http://pbj98r3fm.bkt.clouddn.com/align-items05.jpg)\n\n## Flex 项目属性\n> 1. order\n>    1. 功能：定义项在容器中的排列顺序\n>    2. 参数：Number 即数字\n> 2. flex-basis\n>    1. 参数: length | auto;\n> 3. flex-grow\n>    1. 功能：定义项目的放大比例   \n>    2. 参数：Number\n> 4. flex-shrink\n>    1. 功能：定义了项目的缩小比例\n>    2. 参数：Number\n> 5. flex\n>    1. 功能：flex-grow, flex-shrink 和 flex-basis的简写\n>    2. none | [ <\'flex-grow\'> <\'flex-shrink\'>? || <\'flex-basis\'> ]\n> 6. align-self\n>    1. 功能：允许单个项目有与其他项目不一样的对齐方式\n>    2. auto | flex-start | flex-end | center | baseline | stretch\n\n### order 定义项目在容器中的排列顺序\n定义项目在容器中的排列顺序，数值越小，排列越靠前，默认值为 0\n在 HTML 结构中，虽然 -2，-1 的 item 排在后面，但是由于分别设置了 order，使之能够排到最前面。 \n\n### flex-basis 算主轴是否有多余空间\n定义了在分配多余空间之前，项目占据的主轴空间，浏览器根据这个属性，计算主轴是否有多余空间\n> 当主轴为水平方向的时候，当设置了 flex-basis，项目的宽度设置值会失效，flex-basis 需要跟 flex-grow 和 flex-shrink 配合使用才能发挥效果。\n\n### flex-grow 定义项目的放大比例\n默认值为 0，即如果存在剩余空间，也不放大 \n![](http://pbj98r3fm.bkt.clouddn.com/flex-grow.jpg)\n\n当所有的项目都以 flex-basis 的值进行排列后，仍有剩余空间，那么这时候 flex-grow 就会发挥作用了。 如果所有项目的 flex-grow 属性都为 1，则它们将等分剩余空间。(如果有的话)\n\n如果一个项目的 flex-grow 属性为 2，其他项目都为 1，则前者占据的剩余空间将比其他项多一倍。\n\n当然如果当所有项目以 flex-basis 的值排列完后发现空间不够了，且 flex-wrap：nowrap 时，此时 flex-grow 则不起作用了，这时候就需要接下来的这个属性。\n\n### flex-shrink 定义了项目的缩小比例\n默认值: 1，即如果空间不足，该项目将缩小，负值对该属性无效。 \n![](http://pbj98r3fm.bkt.clouddn.com/flex-shrink.jpg)\n\n这里可以看出，虽然每个项目都设置了宽度为 50px，但是由于自身容器宽度只有 200px，这时候每个项目会被同比例进行缩小，因为默认值为 1。 同理可得：\n\n如果所有项目的 flex-shrink 属性都为 1，当空间不足时，都将等比例缩小。\n\n如果一个项目的 flex-shrink 属性为 0，其他项目都为 1，则空间不足时，前者不缩小\n\n### flex  flex-grow, flex-shrink 和 flex-basis的简写\nflex 的默认值是以上三个属性值的组合。假设以上三个属性同样取默认值，则 flex 的默认值是 0 1 auto。同理，如下是等同的：\n```css\n.item {flex: 2333 3222 234px;}\n.item {\n    flex-grow: 2333;\n    flex-shrink: 3222;\n    flex-basis: 234px;\n}\n```\n\n当 flex 取值为 none，则计算值为 0 0 auto，如下是等同的：\n```css\n.item {flex: none;}\n.item {\n    flex-grow: 0;\n    flex-shrink: 0;\n    flex-basis: auto;\n}\n```\n\n当 flex 取值为 auto，则计算值为 1 1 auto，如下是等同的：\n```css\n.item {flex: auto;}\n.item {\n    flex-grow: 1;\n    flex-shrink: 1;\n    flex-basis: auto;\n}\n```\n\n当 flex 取值为一个非负数字，则该数字为 flex-grow 值，flex-shrink 取 1，flex-basis 取 0%，如下是等同的：\n> flex:1相当于width:100%，就是撑开\n```css\n.item {flex: 1;}\n.item {\n    flex-grow: 1;\n    flex-shrink: 1;\n    flex-basis: 0%;\n}\n```\n\n### align-self: 允许单个项目有与其他项目不一样的对齐方式', '2019-02-11 14:07:17', '2019-02-11 14:07:17');
INSERT INTO `article` VALUES (62, 'canvas', '## 基本用法\n`<canvas>` 两个可选属性 `width` 默认300，`height` 默认150。可以使用`css`属性来设置宽高，但是如果宽高属性和初始比例不一致，就会出现扭曲。\n\n```html\n<style>\n    #canvas{\n        width: 600px;\n        height: 300px;\n        background: #ddd;\n    }\n</style>\n```\n**这种设置`canvas`宽高会让画布变得模糊**\n**这种设置`canvas`宽高会让画布变得模糊**\n**这种设置`canvas`宽高会让画布变得模糊**\n\n`better`\n```html\n<canvas id=\"canvas\" width=\"600\" height=\"300\"></canvas>\n```\n\n```html\n<canvas id=\"canvas\"></canvas>\n<script>\n    const canvas = document.getElementById(\'canvas\')\n    const ctx = canvas.getContext(\'2d\')\n\n    if (canvas.getContext) {\n        ctx.fillStyle = \'red\'\n        ctx.fillRect(0, 0, 200, 200)\n    }\n</script>\n```\n`ctx` : 渲染上下文。绘制都靠他\n\n<!--more-->\n\n## 绘制\n\n### 绘制矩形 fillReact、clearRect、strokeRect \n3种绘制矩形的参数都一样，相对画布的 xy坐标以及绘制的宽高\n- `fillRect(x, y, width, height)`: 绘制一个**填充**的矩形\n- `strokeRect(x, y, width, height)`: 绘制一个矩形的**边框**\n- `clearRect(x, y, width, height)`: 清除指定矩形区域，让清除部分完全透明\n```js\n<canvas id=\"canvas\"></canvas>\n<script>\n    const canvas = document.getElementById(\'canvas\')\n    const ctx = canvas.getContext(\'2d\')\n\n    if (canvas.getContext) {\n        ctx.fillStyle = \'red\'\n        ctx.fillRect(25,25,200,200);\n        ctx.clearRect(45,45,100,100);\n        ctx.strokeRect(50,50,50,50);\n    }\n</script>\n```\n![image.png](https://upload-images.jianshu.io/upload_images/8677726-b6833054414cec15.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 绘制线段 moveTo、lineTo、stoke\n\n#### 基本使用\n- `moveTo(x,y)`: 画笔移到Canvas画布中的某个位置 **（直线的起点）**\n- `lineTo(x,y)`: 把画笔移到另一个点 **（直线的终点）**\n- `stroke()`： 有了起点终点最后需要 `stroke` 方法才可以绘制线段\n\n```js\nctx.moveTo(50, 50)\nctx.lineTo(150, 50)\nctx.stroke()\n```\n上面代码 花了一条横线。默认黑色\n\n#### 线段粗细 lineWidth \n```js\n ctx.lineWidth = 10\nctx.moveTo(50, 50)\nctx.lineTo(150, 50)\nctx.stroke()\n```\n\n#### 线段颜色 strokeStyle\n```js\nctx.strokeStyle = \'#f00\'\n//...\n```\n\n`createLinearGradient` 渐变色 （略）\n\n#### 绘制多条线段 beginPath closePath\n```js\nctx.lineWidth = 10\nctx.strokeStyle = \'#f36\';\nctx.moveTo(50, 50)\nctx.lineTo(150, 50)\nctx.lineTo(150, 150)\nctx.stroke()\n```\n效果图\n![image.png](https://upload-images.jianshu.io/upload_images/8677726-5b8f084c0979fc77.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n```js\nctx.lineWidth = 10\nctx.strokeStyle = \'#f36\';\nctx.beginPath();\nctx.moveTo(50, 50)\nctx.lineTo(150, 50)\nctx.lineTo(150, 150)\nctx.stroke()\nctx.closePath()\nctx.beginPath()\nctx.moveTo(200, 50)\nctx.lineTo(200, 150)\nctx.stroke()\nctx.closePath()\n```\n\n效果图 \n![image.png](https://upload-images.jianshu.io/upload_images/8677726-472e46782a9ffeed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n#### fill 通过填充路径的内容区域生成实心的图形\n`stroke` 绘制线段而`fill` 可以填充！\n如下 可以画出个实心三角形\n```js\nctx.beginPath();\nctx.moveTo(75,50);\nctx.lineTo(100,75);\nctx.lineTo(100,25);\nctx.fill();\n```\n\n[canvas api 网址](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial)', '2019-02-11 14:07:56', '2019-02-11 14:07:56');
INSERT INTO `article` VALUES (64, 'webpack - 入门篇', '\n![](https://user-gold-cdn.xitu.io/2018/9/29/1662310b4cdab44f?w=1071&h=604&f=png&s=62218)\n\n### 什么是 webpack\n\nwebpack 可以看做是模块打包机：他做的事情是，分析你的项目结构，找到 `JavaScript` 模块以及其他的一些浏览器不能直接运行的扩展语言（`Scss`、`TypeScript` 等），将其打包为合适的格式以供浏览器使用\n\n构建就是把源代码转换成发布到线上可执行的 `JavaScript`、CSS、HTML 代码，包括以下内容：\n\n- **代码转换**：`TypeScript` 编译成 `JavaScript`、`SCSS` 编译成 CSS 等等\n- **文件优化**：压缩 `JavaScript`、CSS、HTML 代码，压缩合并图片等\n- **代码分割**：提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载\n- **模块合并**：在采用模块化的项目有很多模块和文件，需要构建功能把模块分类合并成一个文件\n- **自动刷新**：监听本地源代码的变化，自动构建，刷新浏览器\n- **代码校验**：在代码被提交到仓库前需要检测代码是否符合规范，以及单元测试是否通过\n- **自动发布**：更新完代码后，自动构建出线上发布代码并传输给发布系统。\n\n构建其实是工程化、自动化思想在前端开发中的体现。把一系列流程用代码去实现，让代码自动化地执行这一系列复杂的流程。\n\n<!--more-->\n\n#### webpack 的基本概念\n\n- [入口(entry point)](https://www.webpackjs.com/concepts/entry-points/): 指示 webpack 应该使用哪个模块，来作为构建其内部依赖图的开始，webpack 会找出有哪些模块和 library 是入口起点（直接和间接）依赖的。\n\n  - 默认值是 `./src/index.js`，然而，可以通过在 webpack 配置中配置 entry 属性，来指定一个不同的入口起点（或者也可以指定多个入口起点）。\n\n- [出口 output](https://www.webpackjs.com/concepts/output/): 属性告诉 webpack 在哪里输出它所创建的 bundles，以及如何命名这些文件，主输出文件默认为 `./dist/main.js`，其他生成文件的默认输出目录是 `./dist`\n\n- [loader](https://www.webpackjs.com/concepts/loaders/): 让 webpack 能够去处理那些非 JavaScript 文件（webpack 自身只理解 JavaScript）。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块，然后你就可以利用 webpack 的打包能力，对它们进行处理。\n\n> 注意，loader 能够 import 导入任何类型的模块（例如 .css 文件），这是 webpack 特有的功能，其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是有很必要的，因为这可以使开发人员创建出更准确的依赖关系图。\n\n- [插件 plugins](https://www.webpackjs.com/concepts/plugins/): loader 被用于转换某些类型的模块，而插件则可以用于执行范围更广的任务。插件的范围包括，从打包优化和压缩，一直到重新定义环境中的变量。插件接口功能极其强大，可以用来处理各种各样的任务。\n\n- [模式 mode](https://www.webpackjs.com/concepts/mode/): 通过选择 `development` 或 `production` 之中的一个，来设置 mode 参数，你可以启用相应模式下的 webpack 内置的优化\n\n#### webpack 构建过程\n\n1. 从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。\n2. 每找到一个 Module， 就会根据配置的 Loader 去找出对应的转换规则。\n3. 对 Module 进行转换后，再解析出当前 Module 依赖的 Module。\n4. 这些模块会以 Entry 为单位进行分组，一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。\n5. 最后 Webpack 会把所有 Chunk 转换成文件输出。\n6. 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。\n\n### 开发环境和生产环境\n\n我们在日常的前端开发工作中，一般都会有两套构建环境：一套开发时使用，一套供线上使用。\n\n- **development**: 用于开发的配置文件，用于定义 `webpack dev server` 和其他东西\n- **production**: 用于生产的配置文件，用于定义 `UglifyJSPlugin`，`sourcemaps` 等\n\n简单来说，开发时可能需要打印 debug 信息，包含 `sourcemap` 文件，而生产环境是用于线上的即代码都是压缩后，运行时不打印 debug 信息等。譬如 axios、antd 等我们的生产环境中需要使用到那么我们应该安装该依赖在生产环境中，而 `webpack-dev-server` 则是需要安装在开发环境中\n\n平时我们 `npm` 中安装的文件中有 -S -D, -D 表示我们的依赖是安装在开发环境的，而-S 的是安装依赖在生产环境中。\n\n本文就来带你搭建基本的前端开发环境，前端开发环境需要什么呢？\n\n- 构建发布需要的 HTML、CSS、JS、图片等资源\n- 使用 CSS 预处理器，这里使用 less\n- 配置 babel 转码器 => 使用 es6+\n- 处理和压缩图片\n- 配置热加载，HMR\n\n以上配置就可以满足前端开发中需要的基本配置。下面是本文打包后的效果图：\n\n![](https://user-gold-cdn.xitu.io/2018/9/30/16629c066f166b7a?w=1420&h=528&f=png&s=154990)\n\n### 搭建基本的开发环境\n\n### 安装\n\n```\nmkdir webpack-dev && cd webpack-dev\nnpm init -y\nnpm i webpack webpack-cli -D\n```\n\n### 添加 scripts\n\n生成了 package.json 文件，在文件中添加\n\n```json\n \"scripts\": {\n    \"build\": \"webpack --mode production\"\n  }\n```\n\n> --`mode` 模式 (必选，不然会有 `WARNING`)，是 `webpack4` 新增的参数选项，默认是 `production`\n\n- `--mode production` 生产环境\n  - 提供 `uglifyjs-webpack-plugin` 代码压缩\n  - 不需要定义 `new webpack.DefinePlugin({ \"process.env.NODE_ENV\": JSON.stringify(\"production\") })` 默认 `production`\n  - 默认开启 `NoEmitOnErrorsPlugin -> optimization.noEmitOnErrors`, 编译出错时跳过输出，以确保输出资源不包含错误\n  - 默认开启 `ModuleConcatenationPlugin` -> `optimization.concatenateModules`, `webpack3` 添加的作用域提升(`Scope Hoisting`)\n- `--mode development` 开发环境\n  - 使用 eval 构建 module, 提升增量构建速度\n  - 不需要定义 `new webpack.DefinePlugin({ \"process.env.NODE_ENV\": JSON.stringify(\"development\") })` 默认 `development`\n  - 默认开启 `NamedModulesPlugin -> optimization.namedModules` 使用模块热替换(HMR)时会显示模块的相对路径\n\n添加了 scripts 之后，新建`src/index.js`，然后执行`npm run build` ，你就会发现新增了一个 `dist` 目录，里边存放的是 webpack 构建好的 `main.js` 文件。\n\nps [npm scripts 使用指南](http://www.ruanyifeng.com/blog/2016/10/npm_scripts.html)\n\n### 新建 webpack.config.js 文件\n\n要想对 webpack 中增加更多的配置信息，我们需要建立一个 webpack 的配置文件。在根目录下创建 `webpack.config.js` 后再执行 `webpack` 命令，webpack 就会使用这个配置文件的配置了\n\n配置中具备以下的基本信息：\n\n```js\nmodule.exports = {\n  entry: \'\', // 打包入口：指示 webpack 应该使用哪个模块，来作为构建其内部依赖图的开始\n  output: \'\', // 出口\n  resolve: {}, // 配置解析：配置别名、extensions 自动解析确定的扩展等等\n  devServer: {}, // 开发服务器：run dev/start 的配置，如端口、proxy等\n  module: {}, // 模块配置：配置loader（处理非 JavaScript 文件，比如 less、sass、jsx、图片等等）等\n  plugins: [] // 插件的配置：打包优化、资源管理和注入环境变量\n}\n```\n\n#### 配置打包入口和出口\n\n首先我们往 `webpack.config.js` 添加点配置信息\n\n```js\nconst path = require(\'path\')\n\nmodule.exports = {\n  // 指定打包入口\n  entry: \'./src/index.js\',\n\n  // 打包出口\n  output: {\n    path: path.resolve(__dirname, \'dist\'), // 解析路径为 ./dist\n    filename: \'bundle.js\'\n  }\n}\n```\n\n上面我们定义了打包入口 `./src/index.js`，打包出口为 `./dist`, 打包的文件夹名字为`bundle.js`，执行`npm run build`命令后，index.js 文件会被打包为 `bundle.js` 文件。此时随便建立一个 html 文件引用这个`bundle.js`就可以看到你在`index.js` 写的代码了。\n\n[path.resolve([...paths])](http://nodejs.cn/api/path.html#path_path_resolve_paths) 方法会把一个路径或路径片段的序列解析为一个绝对路径。\n\n### 使用 html-webpack-plugin 创建 html 文件\n\n更多情况下我们不希望打包一次，就新建一次 html 文件来引用打包后的文件，这样显得不智能或者说当你打包的文件名修改后，引用路径就会出错。\n\n这个时候我们就可以使用 [html-webpack-plugin](https://webpack.docschina.org/plugins/html-webpack-plugin/) 插件来将 HTML 引用路径和我们的构建结果关联起来。\n\n```\nnpm install html-webpack-plugin -D\n```\n\n创建文件`public/index.html` 修改 `webpack.config.js` 文件\n\n```js\nconst HtmlWebpackPlugin = require(\'html-webpack-plugin\')\nmodule.exports = {\n  //...\n  plugins: [\n    new HtmlWebpackPlugin({\n      filename: \'index.html\', // 配置输出文件名和路径\n      template: \'./public/index.html\' // 配置要被编译的html文件\n    })\n  ]\n}\n```\n\n重新执行 `npm run build`, dist 目录就会多个 `index.html` 并引入了 `bundle.js`.\n\n#### 压缩 html 文件\n\n修改 `webpack.config.js`\n\n```js\nconst HtmlWebpackPlugin = require(\'html-webpack-plugin\')\nmodule.exports = {\n  //...\n  plugins: [\n    new HtmlWebpackPlugin({\n      filename: \'index.html\', // 配置输出文件名和路径\n      template: \'./public/index.html\', // 配置要被编译的html文件\n      hash: true,\n      // 压缩 => production 模式使用\n      minify: {\n        removeAttributeQuotes: true, //删除双引号\n        collapseWhitespace: true //折叠 html 为一行\n      }\n    })\n  ]\n}\n```\n\n### 打包 css 文件\n\n我们希望使用 webpack 来进行构建 css 文件，，为此，需要在配置中引入 loader 来解析和处理 CSS 文件：\n\n```\nnpm install style-loader css-loader -D\n```\n\n新建 `src/assets/style/color.css`, 修改 `webpack.config.js` 文件：\n\n```js\nmodule.exports = {\n  //...\n  module: {\n    /**\n     * test: 匹配特定条件。一般是提供一个正则表达式或正则表达式的数组\n     * include: 匹配特定条件。一般是提供一个字符串或者字符串数组\n     * exclude: 排除特定条件\n     * and: 必须匹配数组中的所有条件\n     * or: 匹配数组中任何一个条件,\n     * nor: 必须排除这个条件\n     */\n    rules: [\n      {\n        test: /\\.css$/,\n        include: [path.resolve(__dirname, \'src\')],\n        use: [\'style-loader\', \'css-loader\']\n      }\n    ]\n  }\n  //...\n}\n```\n\n经由上述两个 loader 的处理后，CSS 代码会转变为 JS， 如果需要单独把 CSS 文件分离出来，我们需要使用 [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) 插件\n\n#### 抽取 css 到独立文件, 自动添加前缀\n\n```\nnpm i mini-css-extract-plugin postcss-loader autoprefixer -D\n```\n\n我们在写 css 时不免要考虑到浏览器兼容问题，如 `transform` 属性，需要添加浏览器前缀以适配其他浏览器。故使用到 `postcss-loader` 这个 loader， 下面则是相关的配置\n\n`webpack.config.js`\n\n```js\nconst MiniCssExtractPlugin = require(\'mini-css-extract-plugin\')\n\nmodule.exports = {\n  // ...\n  module: {\n    rules: [\n      {\n        test: /\\.css$/,\n        include: [path.resolve(__dirname, \'src\')],\n        use: [\n          MiniCssExtractPlugin.loader,\n          \'css-loader\',\n          {\n            loader: \'postcss-loader\',\n            options: {\n              plugins: [require(\'autoprefixer\')]\n            }\n          }\n        ]\n      }\n    ]\n  },\n  plugins: [\n    //...\n    new MiniCssExtractPlugin({\n      filename: \'[name].css\',\n      chunkFilename: \'[id].css\'\n    })\n  ]\n}\n```\n\n#### 打包 less 文件\n\n开发中通常会用到一门预处理语言，这里以`less`为例，通过`less-loader`可以打包 less 为 css 文件\n\n```\nnpm install less less-loader -D\n```\n\n新建 `src/assets/style/index.less`, 并且在 `src/index.js` 中引入 `import \'./assets/style/index.less\'`\n\n配置 `webpack.config.js`\n\n```js\nconst MiniCssExtractPlugin = require(\'mini-css-extract-plugin\')\n\nmodule.exports = {\n  module: {\n    rules: [\n      // ...\n      {\n        test: /\\.less$/,\n        use: [\n          MiniCssExtractPlugin.loader,\n          \'css-loader\',\n          {\n            loader: \'postcss-loader\',\n            options: {\n              plugins: [require(\'autoprefixer\')] // 添加css中的浏览器前缀\n            }\n          },\n          \'less-loader\'\n        ]\n      }\n    ]\n  }\n  //...\n}\n```\n\n执打包命令后就可以发现 `index.less` 中写的样式会和`color.css`一样被打包进 `main.css`中。\n\n[webpack@v4 升级踩坑](https://segmentfault.com/a/1190000014396803?utm_source=tag-newest): 关于使用 `mini-css-extract-plugin` 的注意点。\n\n### 打包图片\n\n```\nnpm install file-loader url-loader -D\n```\n\n**file-loader:** 可以用于处理很多类型的文件，它的主要作用是直接输出文件，把构建后的文件路径返回。\n\n**url-loader:**\n如果图片较多，会发很多 http 请求，会降低页面性能。`url-loader` 会将引入的图片编码，生成 dataURl。相当于把图片数据翻译成一串字符。再把这串字符打包到文件中，最终只需要引入这个文件就能访问图片了。当然，如果图片较大，编码会消耗性能。因此 `url-loader` 提供了一个 limit 参数，小于 limit 字节的文件会被转为 DataURl，大于 limit 的还会使用 `file-loader` 进行 copy。\n\n- url-loader 可以看作是增强版的 file-loader。\n- url-loader 把图片编码成 base64 格式写进页面，从而减少服务器请求。\n\n```js\nmodule.exports = {\n  module: {\n    rules: [\n      // ...\n      {\n        test: /\\.(png|jpg|gif)$/,\n        use: [\n          {\n            loader: \'url-loader\',\n            options: {\n              outputPath: \'images/\', //输出到images文件夹\n              limit: 500 //是把小于500B的文件打成Base64的格式，写入JS\n            }\n          }\n        ]\n      }\n    ]\n  }\n  //...\n}\n```\n\n**url-loader 和 file-loader 是什么关系呢？**\n\n简单地说，`url-loader` 封装了 `file-loader`。`url-loader` 不依赖于 `file-loader`，即使用 `url-loader` 时，只需要安装 `url-loader` 即可，不需要安装 `file-loader`，因为 `url-loader` 内置了 `file-loader`。\n\n通过上面的介绍，我们可以看到，url-loader 工作分两种情况：\n\n- 文件大小小于 limit 参数，url-loader 将会把文件转为 DataURL；\n- 文件大小大于 limit，url-loader 会调用 file-loader 进行处理，参数也会直接传给 file-loader。因此我们只需要安装 url-loader 即可。\n\n有关 `url-loader` 和 `file-loader` 的解析：[webpack 之图片引入-增强的 file-loader：url-loader](https://blog.csdn.net/hdchangchang/article/details/80175782)\n\n### 配置 babel\n\n#### babel-loader\n\n`Babel` 是一个让我们能够使用 ES 新特性的 JS 编译工具，我们可以在 webpack 中配置 Babel，以便使用 ES6、ES7 标准来编写 JS 代码。\n\nBabel 7 的相关依赖包需要加上 `@babel` scope。一个主要变化是 presets 设置由原来的 `env` 换成了 `@babel/preset-env`, 可以配置 `targets`, `useBuiltIns` 等选项用于编译出兼容目标环境的代码。其中 `useBuiltIns` 如果设为 `\"usage\"`，Babel 会根据实际代码中使用的 ES6/ES7 代码，以及与你指定的 targets，按需引入对应的 `polyfill`，而无需在代码中直接引入 `import \'@babel/polyfill\'`，避免输出的包过大，同时又可以放心使用各种新语法特性。\n\n```\nnpm i babel-loader @babel/core @babel/preset-env -D\n```\n\n笔者这里配的版本号如下\n\n```json\n{\n  \"babel-loader\": \"^8.0.4\",\n  \"@babel/core\": \"^7.1.2\",\n  \"@babel/preset-env\": \"^7.1.0\"\n}\n```\n\n- [babel-loader](https://www.npmjs.com/package/babel-loader): 用 babel 转换 ES6 代码需要使用到 `babel-loader`\n- [@babel-preset-env](https://www.npmjs.com/package/@babel/preset-env)： 默认情况下是等于 ES2015 + ES2016 + ES2017，也就是说它对这三个版本的 ES 语法进行转化。\n- [@babel/core](https://www.npmjs.com/package/@babel/core)：babel 核心库\n\n根目录下新建 `.babelrc` 文件\n\n```json\n{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"modules\": false,\n        \"targets\": {\n          \"browsers\": [\"> 1%\", \"last 2 versions\", \"not ie <= 8\"]\n        },\n        \"useBuiltIns\": \"usage\"\n      }\n    ]\n  ]\n}\n```\n\n- presets 是一堆 plugins 的预设，起到方便的作用。\n- plugins 是编码转化工具，babel 会根据你配置的插件对代码进行相应的转化。\n\n修改 `webpack.config.js`\n\n```js\nmodule.exports = {\n  module: {\n    rules: [\n      //...\n      {\n        test: /\\.m?js$/,\n        exclude: /(node_modules|bower_components)/,\n        use: {\n          loader: \'babel-loader\'\n        }\n      }\n    ]\n  }\n}\n```\n\n#### babel/polyfill 和 transform-runtime\n\n> Babel 默认只转换新的 JavaScript 句法（syntax），而不转换新的 API ，比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象，以及一些定义在全局对象上的方法（比如 Object.assign）都不会转码。\n\n- babel-polyfill: 如上述所说，对于新的 API，你可能需要引入 babel-polyfill 来进行兼容\n- 关键点\n\n  - babel-polyfill 是为了模拟一个完整的 ES2015+环境，旨在用于应用程序而不是库/工具。\n  - babel-polyfill 会污染全局作用域\n\nbabel-runtime 的作用：\n\n- **提取辅助函数**。ES6 转码时，babel 会需要一些辅助函数，例如 \\_extend。babel 默认会将这些辅助函数内联到每一个 js 文件里， babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块 babel-runtime 中，这样做能减小项目文件的大小。\n- **提供 polyfill**：不会污染全局作用域，但是不支持实例方法如 Array.includes\n\n`babel-runtime` 更像是分散的 polyfill 模块，需要在各自的模块里单独引入，借助 `transform-runtime` 插件来自动化处理这一切，也就是说你不要在文件开头 import 相关的 `polyfill`，你只需使用，`transform-runtime` 会帮你引入。\n\n对于开发应用来说，直接使用上述的按需 `polyfill` 方案是比较方便的，但如果是开发工具、库的话，这种方案未必适合（`babel-polyfill` 是通过向全局对象和内置对象的 `prototype` 上添加方法实现的，会造成全局变量污染）。Babel 提供了另外一种方案 `transform-runtime`，它在编译过程中只是将需要 `polyfill` 的代码引入了一个指向 `core-js` 中对应模块的链接(alias)。关于这两个方案的具体差异和选择，可以自行搜索相关教程，这里不再展开，下面提供一个 `transform-runtime` 的参考配置方案。\n\n- 首先安装 runtime 相关依赖\n\n```\nnpm i @babel/plugin-transform-runtime -D\nnpm i @babel/runtime -S\n```\n\n修改 `.babelrc`\n\n```json\n{\n  //...\n  \"plugins\": [\"@babel/plugin-transform-runtime\"]\n}\n```\n\n### 打包前清理源目录文件 clean-webpack-plugin\n\n每次打包，都会生成项目的静态资源，随着某些文件的增删，我们的 dist 目录下可能产生一些不再使用的静态资源，webpack 并不会自动判断哪些是需要的资源，为了不让这些旧文件也部署到生产环境上占用空间，所以在 webpack 打包前最好能清理 dist 目录。\n\n```\nnpm install clean-webpack-plugin -D\n```\n\n修改 `webpack.config.js` 文件\n\n```js\nconst CleanWebpackPlugin = require(\'clean-webpack-plugin\')\nmodule.exports = {\n  plugins: [new CleanWebpackPlugin([\'dist\'])]\n}\n```\n\n### 提取公用代码\n\n假如你 `a.js` 和 `b.js` 都 import 了 `c.js` 文件，这段代码就冗杂了。为什么要提取公共代码，简单来说，就是减少代码冗余，提高加载速度。\n\n```js\nmodule.exports = {\n  //...\n  optimization: {\n    splitChunks: {\n      cacheGroups: {\n        commons: {\n          // 抽离自己写的公共代码\n          chunks: \'initial\',\n          name: \'common\', // 打包后的文件名，任意命名\n          minChunks: 2, //最小引用2次\n          minSize: 0 // 只要超出0字节就生成一个新包\n        },\n        styles: {\n          name: \'styles\', // 抽离公用样式\n          test: /\\.css$/,\n          chunks: \'all\',\n          minChunks: 2,\n          enforce: true\n        },\n        vendor: {\n          // 抽离第三方插件\n          test: /node_modules/, // 指定是node_modules下的第三方包\n          chunks: \'initial\',\n          name: \'vendor\', // 打包后的文件名，任意命名\n          // 设置优先级，防止和自定义的公共代码提取时被覆盖，不进行打包\n          priority: 10\n        }\n      }\n    }\n  }\n}\n```\n\n### hash\n\nhash 是干嘛用的？\n我们每次打包出来的结果可能都是同一个文件，那我上线的时候是不是要替换掉上线的 js，那我怎么知道哪是最新的呢，我们一般会清一下缓存。而 hash 就是为了解决这个问题而存在的\n\n我们此时在改一些 webpack.config.js 的配置\n\n```js\nmodule.exports = {\n  //...\n  output: {\n    path: path.resolve(__dirname, \'dist\'),\n    filename: \'[name].[hash:8].js\'\n  },\n  //...\n  plugins: [\n    new MiniCssExtractPlugin({\n      filename: \'[name].[hash:8].css\',\n      chunkFilename: \'[id].[hash:8].css\'\n    })\n  ]\n}\n```\n\n### 减少 resolve 的解析，配置别名\n\n如果我们可以精简 `resolve` 配置，让 `webpack` 在查询模块路径时尽可能快速地定位到需要的模块，不做额外的查询工作，那么 `webpack` 的构建速度也会快一些\n\n```js\nmodule.exports = {\n  resolve: {\n    /**\n     * alias: 别名的配置\n     *\n     * extensions: 自动解析确定的扩展,\n     *    比如 import \'xxx/theme.css\' 可以在extensions 中添加 \'.css\'， 引入方式则为 import \'xxx/theme\'\n     *    @default [\'.wasm\', \'.mjs\', \'.js\', \'.json\']\n     *\n     * modules 告诉 webpack 解析模块时应该搜索的目录\n     *   如果你想要添加一个目录到模块搜索目录，此目录优先于 node_modules/ 搜索\n     *   这样配置在某种程度上可以简化模块的查找，提升构建速度 @default node_modules 优先\n     */\n    alias: {\n      \'@\': path.resolve(__dirname, \'src\'),\n      tool$: path.resolve(__dirname, \'src/utils/tool.js\') // 给定对象的键后的末尾添加 $，以表示精准匹配\n    },\n    extensions: [\'.wasm\', \'.mjs\', \'.js\', \'.json\', \'.jsx\'],\n    modules: [path.resolve(__dirname, \'src\'), \'node_modules\']\n  }\n}\n```\n\n### webpack-dev-serve\n\n上面讲到了都是如何打包文件，但是开发中我们需要一个本地服务，这时我们可以使用 `webpack-dev-server` 在本地开启一个简单的静态服务来进行开发。\n\n`webpack-dev-server` 是 webpack 官方提供的一个工具，可以基于当前的 webpack 构建配置快速启动一个静态服务。当 `mode` 为 `development` 时，会具备 `hot reload` 的功能，即当源码文件变化时，会即时更新当前页面，以便你看到最新的效果。...\n\n```\nnpm install webpack-dev-server -D\n```\n\npackage.json 中 scripts 中添加\n\n```\n\"start\": \"webpack-dev-server --mode development\"\n```\n\n默认开启一个本地服务的窗口 http://localhost:8080/ 便于开发\n\n#### 配置开发服务器\n\n我们可以对 `webpack-dev-server` 做针对性的配置\n\n```js\nmodule.exports = {\n  // 配置开发服务器\n  devServer: {\n    port: 1234,\n    open: true, // 自动打开浏览器\n    compress: true // 服务器压缩\n    //... proxy、hot\n  }\n}\n```\n\n- contentBase: 服务器访问的根目录（可用于访问静态资源）\n- port: 端口\n- open: 自动打开浏览器\n\n### 模块热替换(hot module replacement)\n\n模块热替换(`HMR - Hot Module Replacement`)功能会在应用程序运行过程中替换、添加或删除模块，而无需重新加载整个页面。主要是通过以下几种方式，来显著加快开发速度：\n\n- 保留在完全重新加载页面时丢失的应用程序状态。\n- 只更新变更内容，以节省宝贵的开发时间。\n- 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。\n\n上面我们 `npm start` 后修改一次文件，页面就会刷新一次。这样就存在很大问题了，比如我们使用 `redux`, `vuex` 等插件，页面一刷新那么存放在 `redux`, `vuex` 中的东西就会丢失，非常不利于我们的开发。\n\nHMR 配合 webpack-dev-server ，首先我们配置下 webpack.config.js\n\n```js\nconst webpack = require(\'webpack\')\n\nmodule.exports = {\n  devServer: {\n    //...\n    hot: true\n  },\n  plugins: [\n    new webpack.HotModuleReplacementPlugin()\n    //...\n  ]\n}\n```\n\n配置后还不行，因为 webpack 还不知道你要更新哪里, 修改 `src/index.js` 文件, 添加\n\n```js\nif (module.hot) {\n  module.hot.accept()\n}\n```\n\n重启服务，`npm start` 之后，修改引入 `index.js` 文件后，页面就不会重新刷新了，这便实现了 HMR\n\n但是但是有个问题是，你修改 css/less 等样式文件并未发生改变， what ?\n\nHMR 修改样式表 需要借助于 `style-loader`， 而我们之前用的是 `MiniCssExtractPlugin.loader`， 这也好办，修改其中一个 rules 就可以了，我们可以试试改\n\n```js\nmodule.exports = {\n  module: {\n    rules: [\n      {\n        test: /\\.less$/,\n        use: [\n          // MiniCssExtractPlugin.loader,\n          \'style-loader\',\n          \'css-loader\',\n          {\n            loader: \'postcss-loader\',\n            options: {\n              plugins: [require(\'autoprefixer\')] // 添加css中的浏览器前缀\n            }\n          },\n          \'less-loader\'\n        ]\n      }\n    ]\n  }\n}\n```\n\n这样我们修改 less 文件就会发现 HMR 已经实现了。\n\n其实，我们可以发现，dev 下配置的 loader 为 `style-loader` , 而生产环境下则是需要 `MiniCssExtractPlugin.loader`\n\n这就涉及到了不同环境之间的配置。可以通过 `process.env.NODE_ENV` 获取当前是开发环境或者是生产环境，然后配置不同的 loader，这里就不做展开了。下一篇文章打算在做一个 `react-cli` 或者 `vue-cli` 的配置，将开发环境的配置与生产环境的配置分开为不同的文件。\n\n### 结语\n\n前面讲到的知识都是 webpack 的一些基础的知识，更多的资料可以查询[webpack 中文官网](https://webpack.js.org/)，官网讲的比较详细，我这里也是讲最常的配置，也是一篇入门系列的文章，文中涉及的知识点还有很多地方还需要完善，譬如 优化 webpack 的构建速度， 减小打包的体积等等。\n\n学习 `webpack 4.0` 还需要多实践，多瞎搞，笔者也是刚刚学习 webpack 的配置，不对之处请各位指出。\n\n下一篇文章打算从零配置一个脚手架，以加深自己对 webpack 的理解。\n\n本文产生的代码：[webpack-dev](https://github.com/gershonv/my-code-store/tree/master/webpack/webpack-dev)\n\n### 参考\n\n- [webpack4.x 入门一篇足矣](https://juejin.im/post/5b2b9a00e51d45587b48075e#heading-0)\n- [Webpack4 不深不浅的实践教程](https://segmentfault.com/a/1190000014466696?utm_source=index-hottest/*&%5E%25$#articleHeader0)\n- [webpack 之 babel 配置和 HMR](https://juejin.im/post/5b3834e051882574ce2f3dd9)\n- [使用 webpack 4 和 Babel 7 配置 Vue.js 工程模板](https://segmentfault.com/a/1190000015247255)\n- [webpack 4 ：从 0 配置到项目搭建](https://juejin.im/post/5b3daf2ee51d451962727fbe)\n- [webpack 详解](https://juejin.im/post/5aa3d2056fb9a028c36868aa)\n- [手写一个 webpack4.0 配置](https://juejin.im/post/5b4609f5e51d4519596b66a7)\n- [Webpack 4 教程：从零配置到生产发布（2018）](https://juejin.im/entry/5b552985f265da0f697036b2)\n- [Webpack 揭秘——走向高阶前端的必经之路](https://juejin.im/post/5badd0c5e51d450e4437f07a)\n- [珠峰架构师培训公开课 webpack4.0 进阶](https://www.bilibili.com/video/av25439651?from=search&seid=14183256954711376795)\n- [webpack 官网](https://webpack.js.org/)\n', '2019-02-01 05:04:43', '2019-02-23 05:04:43');
INSERT INTO `article` VALUES (65, 'webpack - 理论篇', '# entry\n\n```js\n/**\n * @param {String} - String 时 打包为一个文件，默认包名 main.js\n * @param {Array} - Array 时 webpack会把数组里所有文件打包成一个js文件\n * @param {Object} - Object 时 webpack会把对象里的文件分别打包成多个文件\n *\n */\nmodule.exports = {\n  entry: \'./index.js\',\n\n  entry: [\'./index.js\', \'./about.js\'],\n\n  entry: {\n    app: \'./index.js\',\n    about: \'./about.js\'\n  },\n\n  entry: {\n    app: \'./index.js\',\n    vendors: [\'jquery\'] // 分离第三方库\n  }\n}\n```\n<!--more-->\n\n### vendors 第三方库\n\n// 待补充\n\n## output\n\n> 指示 webpack 如何去输出、以及在哪里输出、输出的格式等\n\n```js\nmodule.exports = {\n  output: {\n    path: path.resolve(__dirname, \'dist\'), // 输出文件的目录\n    filename: \'js/[name].[chunkhash:8].js\', // 打包路径及名称\n    chunkFilename: \'js/[name].[chunkhash:8].js\' // 按需加载\n    // publicPath：文件输出的公共路径，\n    //...\n  }\n}\n```\n\n## resolve\n\n> 配置模块如何解析\n\n- `extensions`：自动解析确定的扩展,省去你引入组件时写后缀的麻烦，\n- `alias`：非常重要的一个配置，它可以配置一些短路径，\n- `modules`：webpack 解析模块时应该搜索的目录，\n- ...\n\n```js\nmodule.exports = {\n  resolve: {\n    extensions: [\'.js\', \'.jsx\', \'.ts\', \'.tsx\', \'.scss\', \'.json\', \'.css\'],\n    alias: {\n      \'@\': path.resolve(__dirname, \'src\')\n    },\n    modules: [path.resolve(__dirname, \'src\'), \'node_modules\']\n  }\n}\n```\n\n## module.rules\n\n- `rules`：也就是之前的 loaders，\n- `test` ： 正则表达式，匹配编译的文件，\n- `exclude`：排除特定条件，如通常会写 `node_modules`，即把某些目录/文件过滤掉，\n- `include`：它正好与 `exclude` 相反，\n- `use -loader` ：必须要有它，它相当于是一个 `test` 匹配到的文件对应的解析器，`babel-loader`、`style-loader`、`sass-loader`、`url-loader` 等等，\n- `use - options`：它与 `loader` 配合使用，可以是一个字符串或对象，它的配置可以直接简写在 `loader` 内一起，它下面还有 `presets`、`plugins` 等属性；\n\n## plugins\n\n// 另一篇文章 webpack - plugins 篇 敬请期待\n\n## devtool\n\n- 控制是否生成，以及如何生成 source map 文件，开发环境下更有利于定位问题，默认 false,\n- 当然它的开启，也会影响编译的速度，所以生产环境一定一定记得关闭；\n- 常用的值：`cheap-eval-source-map`、`eval-source-map`、`cheap-module-eval-source-map`、`inline-cheap-module-source-map` 等等\n\n```js\ndevtool: \'eval-source-map\' // 原始源代码\n```\n\n## webpack-dev-server\n\n- `contentBase` ：告诉服务(dev server)在哪里查找文件，默认不指定会在是当期项目根目录，\n- `historyApiFallback`:可以是 boolean、 object，默认响应的入口文件，包括 404 都会指向这里，object 见下面示例：\n- `compress`：启用 gzip 压缩，\n- `publicPath`：它其实就是 output.publicPath，当你改变了它，即会覆盖了 output 的配置，\n- `stats`： 可以自定控制要显示的编译细节信息，\n- `proxy`：它其实就是 http-proxy-middleware，可以进行处理一些代理的请求。\n\n```js\nconst webpack = require(\'webpack\')\n\nmodule.exports = {\n  devServer: {\n    contentBase:\'./assets\',\n    port: 1234,\n    open: true, // 自动打开浏览器\n    compress: true // 服务器压缩\n    hot: true // 配合 HotModuleReplacementPlugin 使用\n    //... proxy、hot\n  },\n  plugins: [\n    new webpack.HotModuleReplacementPlugin()\n  ]\n}\n```\n\n## optimization\n\n- `optimization` 是 `webpack4` 新增的，主要是用来让开发者根据需要自定义一些优化构建打包的策略配置，\n- `minimize`：true/false,告诉 webpack 是否开启代码最小化压缩，\n- `minimizer`：自定 js 优化配置，会覆盖默认的配置，结合 `UglifyJsPlugin` 插件使用，\n- `removeEmptyChunks`: bool 值，它检测并删除空的块。将设置为 false 将禁用此优化，\n- `nodeEnv`：它并不是 node 里的环境变量，设置后可以在代码里使用 process.env.NODE_ENV === \'development\'来判断一些逻辑，生产环境 `UglifyJsPlugin` 会自动删除无用代码，\n- `splitChunks` ：取代了 CommonsChunkPlugin，自动分包拆分、代码拆分，详细默认配置：\n- 默认配置，只会作用于异步加载的代码块 —— chunks: \'async\'，它有三个值：all,async,initial\n\n```js\nmodule.exports = {\n  // 优化构建打包的策略配置\n  optimization: {\n    minimize: true, // 是否开启代码最小化压缩 默认 false\n    //splitChunks 默认配置\n    splitChunks: {\n      chunks: \'async\',\n      minSize: 30000,\n      maxSize: 0,\n      minChunks: 1,\n      maxAsyncRequests: 5,\n      maxInitialRequests: 3,\n      automaticNameDelimiter: \'~\',\n      name: true,\n      cacheGroups: {\n        vendors: {\n          test: /[\\\\/]node_modules[\\\\/]/,\n          priority: -10\n        },\n        default: {\n          minChunks: 2,\n          priority: -20,\n          reuseExistingChunk: true\n        }\n      }\n    }\n  }\n}\n```\n\n### 配合 UglifyJsPlugin\n\n```js\nconst UglifyJsPlugin = require(\'uglifyjs-webpack-plugin\')\n\nmodule.exports = {\n  // 优化构建打包的策略配置\n  optimization: {\n    minimizer: [\n      new UglifyJsPlugin({\n        cache: true, // 开启缓存\n        parallel: true, // 开启多线程编译\n        sourceMap: true, // 是否sourceMap\n        uglifyOptions: {\n          // 丑化参数\n          comments: false,\n          warnings: false,\n          compress: {\n            unused: true,\n            dead_code: true,\n            collapse_vars: true,\n            reduce_vars: true\n          },\n          output: {\n            comments: false\n          }\n        }\n      })\n    ]\n  }\n}\n```\n\n## 参考\n\n- [webpack4 配置详解之慢嚼细咽](https://juejin.im/post/5be64a7bf265da615304493e#heading-9)\n', '2019-02-01 05:05:19', '2019-02-23 05:05:19');
INSERT INTO `article` VALUES (66, 'webpack - babel篇', '\n> [Babel](https://babeljs.io/docs/en/) 是一个让我们能够使用 ES 新特性的 JS 编译工具，我们可以在 `webpack` 中配置 `Babel`，以便使用 ES6、ES7 标准来编写 JS 代码。\n\n本文以当前最新版本的 [babel - 7.10](https://babeljs.io/docs/en/) 为例， 做 `babel` 的配置. 相关版本号如下\n\n```json\n{\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.1.6\",\n    \"@babel/plugin-proposal-decorators\": \"^7.1.6\",\n    \"@babel/plugin-transform-runtime\": \"^7.1.0\",\n    \"@babel/preset-env\": \"^7.1.6\",\n    \"@babel/runtime\": \"^7.1.5\",\n    \"babel-loader\": \"^8.0.4\",\n    \"webpack\": \"^4.26.1\",\n    \"webpack-cli\": \"^3.1.2\"\n  }\n}\n```\n<!--more-->\n\n## babel-loader 和 @babel/core\n\n建立基本的 `webpack` 配置文件\n\n```js\nmkdir webpack-babel => cd  webpack-babel => yarn init -y  // 初始化\nnpm i yarn -g // 安装了yarn可以忽略\nyarn add webpack webpack-cli -D\n\n// package.json 中添加：\n\"scripts\": {\n  \"start\": \"webpack --mode development\",\n  \"build\": \"webpack --mode production\"\n}\n\nyarn add babel-loader @babel/core -D\n```\n\n- [yarn](https://www.npmjs.com/package/yarn) : 和 `npm` 几乎一样，本文使用 `yarn` 安装...\n- [babel-loader](https://www.npmjs.com/package/babel-loader): 转义 js 文件代码的 loader\n- [@babel/core](https://www.npmjs.com/package/@babel/core)：babel 核心库\n\n根目录下添加 `webpack.config.js`\n\n```js\nconst path = require(\'path\')\n\nmodule.exports = {\n  entry: \'./src/index.js\',\n  output: {\n    path: path.resolve(__dirname, \'dist\'),\n    filename: \'[name].[hash:8].js\'\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.m?js$/,\n        exclude: /(node_modules|bower_components)/,\n        use: { loader: \'babel-loader\' } // options 在 .babelrc 定义\n      }\n    ]\n  }\n}\n```\n\n`src/index.js`\n\n```js\nconst func = () => {\n  console.log(\'hello webpack\')\n}\nfunc()\n\nclass User {\n  constructor() {\n    console.log(\'new User\')\n  }\n}\n\nconst user = new User()\n```\n\n执行 `yarn build` 后就可以打包成功，打包后的代码是压缩后的。而 `yarn start` 后的代码是未压缩的。为了使代码可读性高一点，我们可以在`webpack.config.js`添加：\n\n```js\nmodule.exports = {\n  //...\n  devtool: true\n}\n```\n\n## @babel-preset-env\n\n打包后我们可以发现箭头函数并未转化为 `ES5` 语法！\n\n查阅 [babel plugins](https://babeljs.io/docs/en/plugins) 文档，如果要转义箭头函数，需要使用到 `@babel/plugin-transform-arrow-functions` 这个插件\n同理转义 `class` 需要使用 `@babel/plugin-transform-classes`\n\n```\nyarn add @babel/plugin-transform-arrow-functions @babel/plugin-transform-classes -D\n```\n\n根目录下建立 `.babelrc` 文件：\n\n```js\n{\n  \"plugins\": [\n    \"@babel/plugin-transform-arrow-functions\",\n    \"@babel/plugin-transform-classes\"\n  ]\n}\n```\n\n`yarn build` 之后可以看出 箭头函数和类都被转义了。\n\n但是假如你再使用 `async await` 之类的 `es6` 语法，你还得一个个添加，这是不实际的。\n\n[@babel-preset-env](https://babeljs.io/docs/en/babel-preset-env#docsNav) 就整合了这些语法转义插件：\n\n```js\nUsing plugins:\ntransform-template-literals {}\ntransform-literals {}\ntransform-function-name {}\ntransform-arrow-functions {}\ntransform-block-scoped-functions {}\ntransform-classes {}\ntransform-object-super {}\n//...\n```\n\n使用如下：\n\n```\nyarn add @babel-preset-env -D\n```\n\n`.babelrc`\n\n```json\n{\n  \"presets\": [\"@babel/preset-env\"]\n}\n```\n\n## @babel/polyfill\n\n> Babel 默认只转换新的 JavaScript 句法（syntax），而不转换新的 API ，比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象，以及一些定义在全局对象上的方法（比如 Object.assign）都不会转码。\n\n这样就导致了一些新的 API 老版浏览器不兼容。如上述所说，对于新的 API，你可能需要引入 `@babel-polyfill` 来进行兼容\n\n```\nyarn add @babel-polyfill -D\n```\n\n修改 `weboack.config.js`\n\n```js\nmodule.exports = {\n  entry: [\'@babel-polyfill\', \'./src/index.js\']\n}\n```\n\n`yarn build` 发现文件体积大了很多，因为上面的代码表示将 `@babel-polyfill` 的代码也打包进去了。\n\n当然这不是我们希望的，如何按需编译呢？ 我们可以这么做：\n\n`index.js`\n\n```js\nimport \'@babel/polyfill\' // 引入\n\nconst func = () => {\n  console.log(\'hello webpack\')\n}\nfunc()\n\nclass User {\n  constructor() {\n    console.log(\'new User\')\n  }\n}\n\nconst user = new User()\n\nnew Promise(resolve => console.log(\'promise\'))\n\nArray.from(\'foo\')\n```\n\n还原 `webpack.config.js`\n\n```js\nmodule.exports = {\n  entry: \'./src/index.js\'\n}\n```\n\n修改 `.babelrc`\n\n```json\n{\n  \"presets\": [[\"@babel/preset-env\", { \"useBuiltIns\": \"usage\" }]]\n}\n```\n\n`yarn build` 后发现我们的代码体积就变得很小了！\n\n## @babel/runtime 和 @babel/plugin-transform-runtime\n\n- `babel-polyfill` 会污染全局作用域, 如引入 `Array.prototype.includes` 修改了 Array 的原型，除此外还有 String...\n- `babel-polyfill` 引入新的对象： `Promise`、`WeakMap` 等\n\n这也不是我们希望出现的。\n\n- `@babel/runtime` 的作用：\n  - 提取辅助函数。ES6 转码时，babel 会需要一些辅助函数，例如 \\_extend。babel 默认会将这些辅助函数内联到每一个 js 文件里， babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块 `babel-runtime` 中，这样做能减小项目文件的大小。\n  - 提供 `polyfill`：不会污染全局作用域，但是不支持实例方法如 Array.includes\n- `@transform-runtime` 的作用：\n  - `babel-runtime` 更像是分散的 `polyfill` 模块，需要在各自的模块里单独引入，借助 transform-runtime 插件来自动化处理这一切，也就是说你不要在文件开头 import 相关的 `polyfill`，你只需使用，`transform-runtime` 会帮你引入。\n\n```\nyarn add  @babel/runtime-corejs2\nyarn add @babel/plugin-transform-runtime -D\n```\n\n修改 `.babelrc`\n\n```json\n{\n  \"presets\": [\"@babel/preset-env\"],\n  \"plugins\": [[\"@babel/plugin-transform-runtime\", { \"corejs\": 2 }]]\n}\n```\n\n`index.js` 移除 `import \'@babel/polyfill\'`\n\n## @babel/plugin-proposal-decorators\n\n添加装饰器模式的支持\n\n```\nyarn add @babel/plugin-proposal-decorators -D\n```\n\n`index.js`\n\n```js\nfunction annotation(target) {\n  target.annotated = true\n}\n\n@annotation\nclass User {\n  constructor() {\n    console.log(\'new User\')\n  }\n}\n//...\n```\n\n`.babelrc`\n\n```json\n{\n  \"presets\": [\"@babel/preset-env\"],\n  \"plugins\": [\n    [\"@babel/plugin-proposal-decorators\", { \"decoratorsBeforeExport\": true }],\n    [\"@babel/plugin-transform-runtime\", { \"corejs\": 2 }]\n  ]\n}\n\n```\n', '2019-02-01 05:05:52', '2019-02-23 05:05:52');
INSERT INTO `article` VALUES (67, 'webpack - plugins', '## 功能类\n\n### html-webpack-plugin\n\n- 把编译后的文件（css/js）插入到入口文件中，可以只指定某些文件插入，可以对 html 进行压缩等\n- `filename`：输出文件名；\n- `template`：模板文件，不局限于 html 后缀哦；\n- `removeComments`：移除 HTML 中的注释；\n- `collapseWhitespace`：删除空白符与换行符，整个文件会压成一行；\n- `inlineSource`：插入到 html 的 css、js 文件都要内联，即不是以 link、script 的形式引入；\n- `inject`：是否能注入内容到 输出 的页面去；\n- `chunks`：指定插入某些模块；\n- `hash`：每次会在插入的文件后面加上 hash ，用于处理缓存，如：；\n  其他：favicon、meta、title ……；\n\n<!--more-->\n\n```js\nconst path = require(\'path\')\nconst HtmlWebpackPlugin = require(\'html-webpack-plugin\')\nmodule.exports = {\n  mode: \'production\',\n  entry: \'./src/index.js\',\n  output: {\n    path: path.resolve(__dirname, \'dist\'), // 输出文件的目录\n    filename: \'js/[name].[hash:8].js\' // 打包路径及名称\n  },\n  plugins: [\n    new HtmlWebpackPlugin({\n      filename: \'index.html\', // 生成文件名\n      template: \'./public/index.html\', // 配置要被编译的html文件\n      hash: true,\n      // 压缩HTML文件\n      minify: {\n        removeAttributeQuotes: true, //删除双引号\n        collapseWhitespace: true //折叠 html 为一行\n      }\n    })\n  ]\n}\n```\n\n传送门 ==> [html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin)\n\n### clean-webpack-plugin\n\n> 在编译之前清理指定目录指定内容。\n\n```js\nconst CleanWebpackPlugin = require(\'clean-webpack-plugin\')\nmodule.exports = {\n  plugins: [new CleanWebpackPlugin([\'dist\'])]\n}\n\n// 指定清除哪些文件 new CleanWebpackPlugin(pathsToClean [, {pathsToClean }]) 详情请看 npm\n```\n\n传送门 ==> [clean-webpack-plugin](https://www.npmjs.com/package/clean-webpack-plugin)\n\n### copy-webpack-plugin\n\n```js\nconst CopyWebpackPlugin = require(\'copy-webpack-plugin\')\n\nmodule.exports = {\n  plugins: [new CopyWebpackPlugin([...patterns], options)]\n}\n```\n\n传送门 ==> [copy-webpack-plugin](https://www.npmjs.com/package/copy-webpack-plugin)\n\n### compression-webpack-plugin\n\n> 使用 compression-webpack-plugin 插件进行压缩，提供带 `Content-Encoding` 编码的压缩版的资源。\n\n```js\nconst CompressionPlugin = require(\'compression-webpack-plugin\')\nmodule.exports = {\n  plugins: [\n    new CompressionPlugin({\n      filename: \'[path].gz[query]\', //目标资源名称。[file] 会被替换成原资源。[path] 会被替换成原资源路径，[query] 替换成原查询字符串\n      algorithm: \'gzip\', //算法\n      test: /\\.js(\\?.*)?$/i, //压缩 js\n      deleteOriginalAssets: true, // 删除源文件\n      threshold: 10240, //只处理比这个值大的资源。按字节计算\n      minRatio: 0.8 //只有压缩率比这个值小的资源才会被处理\n    })\n  ]\n}\n```\n\n传送门 ==> [compression-webpack-plugin](https://www.npmjs.com/package/compression-webpack-plugin)\n\n### webpack-manifest-plugin\n\n> 该插件可以显示出编译之前的文件和编译之后的文件的映射\n\n```js\nconst ManifestPlugin = require(\'webpack-manifest-plugin\')\nmodule.exports = {\n  plugins: [new ManifestPlugin()]\n}\n```\n\n传送门 ==> [webpack-manifest-plugin](https://www.npmjs.com/package/webpack-manifest-plugin)\n\n### progress-bar-webpack-plugin\n\n> 编译进度条插件\n\n```js\nconst ProgressBarPlugin = require(\'progress-bar-webpack-plugin\') // 编译进度条插件\nmodule.exports = {\n  plugins: [new ProgressBarPlugin()]\n}\n```\n\n传送门 ==> [progress-bar-webpack-plugin](https://www.npmjs.com/package/progress-bar-webpack-plugin)\n\n## 代码相关\n\n### webpack.ProvidePlugin\n\n> 自动加载模块，而不必到处 import 或 require 。\n\n```js\nconst webpack = require(\'webpack\')\nmodule.exports = {\n  plugins: [new webpack.ProvidePlugin({ $: \'jquery\' })]\n}\n\n// index.js\nconsole.log($)\n```\n\n传送门 ==> [webpack.ProvidePlugin](https://www.webpackjs.com/plugins/provide-plugin/)\n\n### webpack.DefinePlugin\n\n> `DefinePlugin` 允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。如果在开发构建中，而不在发布构建中执行日志记录，则可以使用全局常量来决定是否记录日志。这就是 `DefinePlugin` 的用处，设置它，就可以忘记开发和发布构建的规则。\n\n```js\nmodule.exports = {\n  plugins: [\n    new webpack.DefinePlugin({\n      PRODUCTION: JSON.stringify(true)\n    })\n  ]\n}\n\n// index.js\nconsole.log(PRODUCTION) // true\n```\n\n传送门 ==> [webpack.DefinePlugin](https://www.webpackjs.com/plugins/define-plugin/)\n\n### mini-css-extract-plugin\n\n`mini-css-extract-plugin`，它默认就会对你的样式进行模块化拆分。相对 `extract-text-webpack-plugin`。 即 css 异步按需加载\n\n```js\nconst MiniCssExtractPlugin = require(\'mini-css-extract-plugin\') // 抽取 css 到独立文件\n\nmodule.exports = {\n  // ...\n  module: {\n    rules: [\n      {\n        test: /\\.css$/,\n        include: [path.resolve(__dirname, \'src\')],\n        use: [\n          {\n            loader: MiniCssExtractPlugin.loader,\n            options: {\n              publicPath: \'../\' // chunk publicPath\n            }\n          },\n          \'css-loader\'\n        ]\n      }\n    ]\n  },\n\n  plugins: [\n    new MiniCssExtractPlugin({\n      filename: \'css/[name].[hash:8].css\', //这里配置跟output写法一致\n      chunkFilename: \'css/[id][chunkhash:8].css\'\n    })\n  ]\n}\n```\n\n传送门 ==> [mini-css-extract-plugin](https://www.npmjs.com/package/mini-css-extract-plugin)\n\n## 编译结果优化类\n\n### wbepack.IgnorePlugin\n\n防止在 import 或 require 调用时，生成以下正则表达式匹配的模块：\n\n- `requestRegExp` 匹配(test)资源请求路径的正则表达式。\n- `contextRegExp` （可选）匹配(test)资源上下文（目录）的正则表达式。\n\nmoment 2.18 会将所有本地化内容和核心功能一起打包（见该 [GitHub issue](https://github.com/moment/moment/issues/2373)）。你可使用 IgnorePlugin 在打包时忽略本地化内容:\n\n```js\nnew webpack.IgnorePlugin(/^\\.\\/locale$/, /moment$/)\n```\n\n传送门 ==> [wbepack.IgnorePlugin](https://www.webpackjs.com/plugins/ignore-plugin/)\n\n### uglifyjs-webpack-plugin\n\n- js 代码压缩,默认会使用 `optimization.minimizer`，\n- `cache`: Boolean/String ,字符串即是缓存文件存放的路径；\n- `test`：正则表达式、字符串、数组都可以，用于只匹配某些文件，如：/.js(?.\\*)?\\$/i;\n- `parallel` : 启用多线程并行运行来提高编译速度，经常编译的时候听到电脑跑的呼呼响，可能就是它干的，哈哈～；\n- `output.comments` ： 删除所有注释，\n- `compress.warnings` ：插件在进行删除一些无用代码的时候，不提示警告，\n- `compress.drop_console`：喜欢打 console 的同学，它能自动帮你过滤掉，再也不用担心线上还打印日志了；\n\n```js\nconst UglifyJsPlugin = require(\'uglifyjs-webpack-plugin\')\nmodule.exports = {\n  optimization: {\n    minimizer: [\n      new UglifyJsPlugin({\n        cache: true, // 开启缓存\n        parallel: true, // 开启多线程编译\n        sourceMap: true, // 是否sourceMap\n        // 丑化参数\n        uglifyOptions: {\n          comments: false,\n          warnings: false,\n          compress: {\n            unused: true,\n            dead_code: true,\n            collapse_vars: true,\n            reduce_vars: true\n          },\n          output: {\n            comments: false\n          }\n        }\n      })\n    ]\n  }\n}\n```\n\n传送门 ==> [uglifyjs-webpack-plugin](https://www.npmjs.com/package/uglifyjs-webpack-plugin)\n\n### optimize-css-assets-webpack-plugin\n\n它的作用在于压缩 css 文件\n\n- `assetNameRegExp`：默认是全部的 css 都会压缩，该字段可以进行指定某些要处理的文件，\n- `cssProcessor`：指定一个优化 css 的处理器，默认 cssnano，\n- `cssProcessorPluginOptions`：cssProcessor 后面可以跟一个 process 方法，会返回一个 promise 对象，而 cssProcessorPluginOptions 就是一个 options 参数选项！\n- `canPrint`：布尔，是否要将编译的消息显示在控制台，没发现有什么用！\n- 坑点 ：建议使用高版本的包，之前低版本有遇到样式丢失把各浏览器前缀干掉的问题，\n\n```js\nconst OptimizeCssAssetsPlugin = require(\'optimize-css-assets-webpack-plugin\') // 丑化 css\nmodule.exports = {\n  optimization: {\n    minimizer: [\n      new OptimizeCssAssetsPlugin({\n        cssProcessor: require(\'cssnano\'), // css 压缩优化器\n        cssProcessorOptions: { discardComments: { removeAll: true } } // 去除所有注释\n      })\n    ]\n  }\n}\n```\n\n传送门 ==> [optimize-css-assets-webpack-plugin](https://www.npmjs.com/package/optimize-css-assets-webpack-plugin)\n\n### SplitChunksPlugin\n\n`webpack` 把 `chunk` 分为两种类型，一种是初始加载 `initial chunk`，另外一种是异步加载 `async chunk`，如果不配置 ` SplitChunksPlugin，``webpack ` 会在 production 的模式下自动开启，默认情况下，`webpack` 会将 `node_modules` 下的所有模块定义为异步加载模块，并分析你的 `entry`、动态加载（`import()`、require.ensure）模块，找出这些模块之间共用的 `node_modules` 下的模块，并将这些模块提取到单独的 `chunk` 中，在需要的时候异步加载到页面当中，其中默认配置如下：\n\n```js\nmodule.exports = {\n  //...\n  optimization: {\n    splitChunks: {\n      chunks: \'async\', // 异步加载chunk\n      minSize: 30000,\n      maxSize: 0,\n      minChunks: 1,\n      maxAsyncRequests: 5,\n      maxInitialRequests: 3,\n      automaticNameDelimiter: \'~\', // 文件名中chunk分隔符\n      name: true,\n      cacheGroups: {\n        vendors: {\n          test: /[\\\\/]node_modules[\\\\/]/, //\n          priority: -10\n        },\n        default: {\n          minChunks: 2, // 最小的共享chunk数\n          priority: -20,\n          reuseExistingChunk: true\n        }\n      }\n    }\n  }\n}\n```\n\n传送门 ==> [SplitChunksPlugin](https://www.webpackjs.com/plugins/split-chunks-plugin/)\n\n### webpack.HotModuleReplacementPlugin\n\n热更新, 配合 `webpack-dev-server` 使用\n\n```js\nyarn add webpack-dev-server -D\n```\n\n```js\nnew webpack.HotModuleReplacementPlugin()\n\nmodule.exports = {\n  devServer: {\n    port: 1234,\n    open: true, // 自动打开浏览器\n    compress: true, // 服务器压缩\n    hot: true // 开启热加载\n    //... proxy、hot\n  },\n  plugins: [new webpack.HotModuleReplacementPlugin()]\n}\n\n// index.js\n\nif (module.hot) {\n  module.hot.accept()\n}\n```\n\n- 传送门 ==> [devServer](https://www.webpackjs.com/configuration/dev-server/)\n- 传送门 ==> [webpack-dev-server](https://www.npmjs.com/package/webpack-dev-server)\n\n## 加快编译速度\n\n### DllPlugin && DllReferencePlugin && autodll-webpack-plugin\n\n`DllPlugin` 和 `DllReferencePlugin` 提供分离包的方式可以大大提高构建时间性能。主要思想在于，将一些不做修改的依赖文件，提前打包，这样我们开发代码发布的时候就不需要再对这部分代码进行打包。从而节省了打包时间。\n\nDllPlugin 插件：用于打包出一个个单独的动态链接库文件。\nDllReferencePlugin 插件：用于在主要配置文件中去引入 DllPlugin 插件打包好的动态链接库文件。\n\n- `DllPlugin`\n  - `context (optional)`: manifest 文件中请求的上下文(context)(默认值为 webpack 的上下文(context))\n  - `name`: 暴露出的 DLL 的函数名 (TemplatePaths: [hash] & [name] )\n  - `path`: manifest json 文件的绝对路径 (输出文件)\n\n`DllReferencePlugin`: 这个插件把只有 dll 的 bundle(们)(dll-only-bundle(s)) 引用到需要的预编译的依赖。\n\n- `DllReferencePlugin`\n  - `context`: (绝对路径) manifest (或者是内容属性)中请求的上下文\n  - `manifest`: 包含 content 和 name 的对象，或者在编译时(compilation)的一个用于加载的 JSON manifest 绝对路径\n  - `content (optional)`: 请求到模块 id 的映射 (默认值为 manifest.content)\n  - `name (optional)`: dll 暴露的地方的名称 (默认值为 manifest.name) (可参考 externals)\n  - `scope (optional)`: dll 中内容的前缀\n  - `sourceType (optional)`: dll 是如何暴露的 (libraryTarget)\n\n```js\n```\n\n传送门 ==> [DllPlugin](https://www.webpackjs.com/plugins/dll-plugin/)\n', '2019-02-01 05:06:36', '2019-02-23 05:06:36');
INSERT INTO `article` VALUES (68, 'git 实用指南', '\n## commit 规范速查\n\n- `feat`：新功能（feature）\n- `fix`：修补 bug\n- `docs`：文档（documentation）\n- `style`： 格式（不影响代码运行的变动）\n- `refactor`：重构（即不是新增功能，也不是修改 bug 的代码变动）\n- `test`：增加测试\n- `chore`：构建过程或辅助工具的变动\n- `revert`: 撤销以前的 commit\n \n ```bash\n revert: feat(pencil): add \'graphiteWidth\' option\n ```\n\n<!--more-->\n\n## 本地创建、连接远程仓库\n\n```bash\n# 创建并连接远程仓库\nmkdir git-demo\n\ncd git-demo/\n\ngit init\n\n# 连接远程仓库\ngit remote add origin https://github.com/gershonv/git-demo.git\n```\n\n## 新建文件并推向远端\n\n```bash\n# 创建 a.js\ntouch a.js\n\n# 添加到暂存区（见下文）\ngit add .\n\n# commit 记录（见下文）\ngit commit -m \'feat: 新增 a.js 文件\'\n\n# 推向远端 master 分支（见下文）\ngit push origin master\n```\n\n- git add\n  - `git add [file1 file2 file3...]`: 添加多个文件\n  - `git add .` : 暂存所有文件\n\n## git status\n\n![](https://user-gold-cdn.xitu.io/2019/1/8/1682b86ab859defb?w=505&h=412&f=png&s=45734)\n\nM - 被修改，A - 被添加，D - 被删除，R - 重命名，?? - 未被跟踪\n\n## 撤销操作\n\n### 撤销 git add\n\n```bash\n# 新建 b.js 文件\ntouch b.js\n\ngit add .\n\ngit statis\n\n# 撤销 git add\ngit reset head b.js\n```\n\n- `git reset head` : 如果后面什么都不跟的话 就是上一次 add 里面的全部撤销了\n- `git reset head file`: 对某个文件进行撤销了\n\n### 撤销本地修改\n\n```bash\n# 修改文件\nvim a.js\n\n# 插入数据\nshift + i\n\n# 保存退出\nshift + : wq\n\n# 加入暂存区\ngit add .\n\ngit commit -m \'refactor: 修改 a.js 文件\'\n\n# 撤销修改\ngit checkout -- a.js\n```\n\n### 撤销 git commit\n\n```bash\n# 查看 commit 记录\ngit log\n\n# 重置到某个节点。\ngit reset --hard ea794cf0dcf934b594\n```\n\n## 分支\n\n### 新建分支并推向远程\n\n```bash\n# 新建并切换本地分支\ngit checkout -b dev\n\n# 查看当前分支\ngit branch\n\n# 查看远程分支\ngit branch -r\n\n# 推送到远程\ngit push origin dev\n```\n\n### 合并分支\n\n```bash\n# 开发完 dev 分支后\ngit checkout master\n\n# 合并 dev 分支到主分支\ngit merge dev\n\n# 推送\ngit push origin master\n```\n\n### 分支管理\n\n```bash\n# 查看所有分支 远程+本地\ngit branch -a\n\n# 删除远程分支\ngit push origin -d dev\n\n# 删除本地分支\ngit checkout master\ngit branch -d dev\n```', '2019-02-02 05:08:12', '2019-02-23 05:08:12');
INSERT INTO `article` VALUES (69, 'koa2-基础知识', '## 起步\n\n```\nnpm i koa -S\n```\n\n```js\nconst Koa = require(\'koa\')\n\nconst app = new Koa()\n\napp.use(async ctx => {\n  ctx.body = \'hello koa\'\n})\n\napp.listen(3000, () => {\n  console.log(\'app listen on http://127.0.0.1:3000\')\n})\n```\n<!--more-->\n\n## 接收 get / post 请求\n\n### get => ctx.query\n\n```js\napp.use(async ctx => {\n  /**\n   * test: http://localhost:3000/?username=guodada\n   * url: /?username=guodada\n   * query: {username: \"guodada\"}\n   * querystring: username=guodada\n   * request: { header, method, url}\n   */\n  let { url, request, query, querystring } = ctx\n  ctx.body = { url, request, query, querystring }\n})\n```\n\n- `query`：返回的是格式化好的参数对象。\n- `querystring`：返回的是请求字符串。\n\n### post => ctx.request.body\n\n对于 `POST` 请求的处理，`Koa2` 没有封装方便的获取参数的方法，需要通过解析上下文 `context` 中的原生 `node.js` 请求对象 `req` 来获取。\n\n```js\n// 将 useraname=guodada&age=22 解析为 { \"useraname\": \"guodada\", \"age\":  22 }\nconst parseQueryStr = queryStr => {\n  let queryData = {}\n  let queryStrList = queryStr.split(\'&\')\n  for (let [index, queryStr] of queryStrList.entries()) {\n    let itemList = queryStr.split(\'=\')\n    queryData[itemList[0]] = decodeURIComponent(itemList[1])\n  }\n  return queryData\n}\n\n// 解析 post 得到的数据\nconst parsePostData = ctx => {\n  return new Promise((resolve, reject) => {\n    try {\n      let postString = \'\'\n      ctx.req.on(\'data\', chunk => {\n        postString += chunk\n      })\n      ctx.req.on(\'end\', chunk => {\n        // postString : useraname=guodada&age=22\n        let parseData = parseQueryStr(postString)\n        resolve(parseData)\n      })\n    } catch (error) {\n      reject(error)\n    }\n  })\n}\n\n// 测试用例：post : { \"useraname\": \"guodada\", \"age\":  22 }\napp.use(async ctx => {\n  if (ctx.method === \'POST\') {\n    const data = await parsePostData(ctx)\n    ctx.body = data\n  }\n})\n```\n\n### koa-bodyparser\n\n上面解析 `post` 数据就是 `koa-bodyparser` 中间件函数的雏形，不需要我们写，直接使用这个中间件就可以了\n\n```\nnpm i koa-bodyparser -S\n```\n\n```js\nconst Koa = require(\'koa\')\nconst bodyParser = require(\'koa-bodyparser\')\n\nconst app = new Koa()\n\napp.use(bodyParser())\n\napp.use(async ctx => {\n  ctx.body = ctx.request.body\n})\n\n//...\n```\n\n## koa-router\n\n主要是通过 `ctx.request.url` 获取地址栏输入的路径，根据路径不同进行跳转。这里不做实现。使用已有的中间件 `koa-router`\n\n```\nnpm install koa-router -S\n```\n\n```js\nconst Router = require(\'koa-router\')\nconst router = new Router()\n\nrouter.get(\'/\', function(ctx, next) {\n  ctx.body = \'Hello index\'\n})\n\napp\n  .use(router.routes()) // 作用：启动路由\n  .use(router.allowedMethods()) // 作用：这是官方推荐用法，我们可以看到 router.allowedMethods() 用在路由匹配\n// router.routes() 之后，所以在所有路由中间件最后调用，此时根据 ctx.status 设置 response 响应头\n```\n\n### 路由层级\n\n```js\nconst router = new Router()\nconst page = new Router()\n\n// page 路由\npage\n  .get(\'/info\', async ctx => {\n    ctx.body = \'url: /page/info\'\n  })\n  .get(\'/todo\', async ctx => {\n    ctx.body = \'url: /page/todo\'\n  })\n\nrouter.use(\'/page\', page.routes(), page.allowedMethods())\n\napp.use(router.routes()).use(router.allowedMethods())\n\n// 输入 localhost:3000/page/info 可以去到 page 的路由...\n```\n\n## cookie\n\n`koa` 的上下文（`ctx`）直接提供了 `cookie` 读取和写入的方法\n\n- `ctx.cookies.get(name,[optins])`:读取上下文请求中的 cookie。\n- `ctx.cookies.set(name,value,[options])`：在上下文中写入 cookie。\n\n```js\napp.use(async ctx => {\n  ctx.cookies.set(\'username\', \'guodada\', {\n    domain: \'127.0.0.1\', // 写cookie所在的域名\n    path: \'/\', // 写cookie所在的路径\n    maxAge: 1000 * 60 * 60 * 24, // cookie有效时长\n    expires: new Date(\'2018-12-31\'), // cookie失效时间\n    httpOnly: false, // 是否只用于http请求中获取 默认是 true\n    overwrite: false // 是否允许重写 默认是 false\n  })\n  ctx.body = ctx.cookies.get(\'username\') // guodada\n})\n```\n\n## koa-static 静态资源中间件\n\n```js\nconst path = require(\'path\')\nconst static = require(\'koa-static\')\nconst staticPath = \'./static\'\n\napp.use(static(\n  path.join( __dirname,  staticPath)\n))\n```\n\n我们 新建 `static/baidu.png`, 地址输入 `localhost:3000/baidu.png` 就可以直接访问静态资源了。', '2019-02-23 05:09:15', '2019-02-23 05:09:15');
COMMIT;

-- ----------------------------
-- Table structure for category
-- ----------------------------
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `articleId` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `articleId` (`articleId`),
  CONSTRAINT `category_ibfk_1` FOREIGN KEY (`articleId`) REFERENCES `article` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of category
-- ----------------------------
BEGIN;
INSERT INTO `category` VALUES (3, 'MySQL', 1);
INSERT INTO `category` VALUES (5, 'MySQL', 2);
INSERT INTO `category` VALUES (7, 'MySQL', 3);
INSERT INTO `category` VALUES (8, 'MySQL', 4);
INSERT INTO `category` VALUES (29, 'React', 15);
INSERT INTO `category` VALUES (30, 'React', 16);
INSERT INTO `category` VALUES (37, 'Sequelize', 23);
INSERT INTO `category` VALUES (38, 'Sequelize', 24);
INSERT INTO `category` VALUES (39, 'Sequelize', 25);
INSERT INTO `category` VALUES (40, 'Sequelize', 26);
INSERT INTO `category` VALUES (41, 'Sequelize', 27);
INSERT INTO `category` VALUES (42, 'Sequelize', 28);
INSERT INTO `category` VALUES (43, 'Vue', 29);
INSERT INTO `category` VALUES (44, 'Vue', 30);
INSERT INTO `category` VALUES (45, 'Vue', 31);
INSERT INTO `category` VALUES (46, 'Vue', 32);
INSERT INTO `category` VALUES (47, 'Javascript', 33);
INSERT INTO `category` VALUES (48, 'Javascript', 34);
INSERT INTO `category` VALUES (49, 'Javascript', 35);
INSERT INTO `category` VALUES (50, 'Javascript', 36);
INSERT INTO `category` VALUES (51, 'HTTP', 37);
INSERT INTO `category` VALUES (52, 'HTTP', 38);
INSERT INTO `category` VALUES (53, 'HTTP', 39);
INSERT INTO `category` VALUES (54, 'HTTP', 40);
INSERT INTO `category` VALUES (55, 'HTTP', 41);
INSERT INTO `category` VALUES (56, 'HTTP', 42);
INSERT INTO `category` VALUES (57, 'Javascript', 43);
INSERT INTO `category` VALUES (58, 'Javascript', 44);
INSERT INTO `category` VALUES (59, 'Javascript', 45);
INSERT INTO `category` VALUES (60, 'Javascript', 46);
INSERT INTO `category` VALUES (61, 'Javascript', 47);
INSERT INTO `category` VALUES (62, 'Javascript', 48);
INSERT INTO `category` VALUES (63, 'Javascript', 49);
INSERT INTO `category` VALUES (64, 'Javascript', 50);
INSERT INTO `category` VALUES (65, 'Javascript', 51);
INSERT INTO `category` VALUES (66, 'Javascript', 52);
INSERT INTO `category` VALUES (67, 'Javascript', 53);
INSERT INTO `category` VALUES (68, 'Javascript', 54);
INSERT INTO `category` VALUES (69, 'Javascript', 55);
INSERT INTO `category` VALUES (70, 'Javascript', 56);
INSERT INTO `category` VALUES (71, 'Javascript', 57);
INSERT INTO `category` VALUES (72, 'Javascript', 58);
INSERT INTO `category` VALUES (73, 'Javascript', 59);
INSERT INTO `category` VALUES (74, 'Javascript', 60);
INSERT INTO `category` VALUES (75, 'HTML-CSS', 61);
INSERT INTO `category` VALUES (76, 'HTML-CSS', 62);
INSERT INTO `category` VALUES (77, 'Javascript', NULL);
INSERT INTO `category` VALUES (78, 'React', 22);
INSERT INTO `category` VALUES (79, 'React', 21);
INSERT INTO `category` VALUES (80, 'React', 20);
INSERT INTO `category` VALUES (81, 'React', 19);
INSERT INTO `category` VALUES (82, 'React', 18);
INSERT INTO `category` VALUES (83, 'React', 17);
INSERT INTO `category` VALUES (84, 'React', 14);
INSERT INTO `category` VALUES (85, 'React', 13);
INSERT INTO `category` VALUES (86, 'React', 12);
INSERT INTO `category` VALUES (87, 'React', 11);
INSERT INTO `category` VALUES (88, 'React', 10);
INSERT INTO `category` VALUES (89, 'React', 9);
INSERT INTO `category` VALUES (90, 'React', 8);
INSERT INTO `category` VALUES (91, 'React', 7);
INSERT INTO `category` VALUES (92, 'React', 6);
INSERT INTO `category` VALUES (93, 'React', 5);
INSERT INTO `category` VALUES (94, 'webpack', 64);
INSERT INTO `category` VALUES (95, 'webpack', 65);
INSERT INTO `category` VALUES (96, 'webpack', 66);
INSERT INTO `category` VALUES (97, 'webpack', 67);
INSERT INTO `category` VALUES (98, 'git', 68);
INSERT INTO `category` VALUES (99, 'koa2', 69);
COMMIT;

-- ----------------------------
-- Table structure for comment
-- ----------------------------
DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `articleId` int(11) DEFAULT NULL,
  `content` text,
  `createdAt` datetime DEFAULT NULL,
  `updatedAt` datetime DEFAULT NULL,
  `userId` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `articleId` (`articleId`),
  KEY `userId` (`userId`),
  CONSTRAINT `comment_ibfk_1` FOREIGN KEY (`articleId`) REFERENCES `article` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `comment_ibfk_2` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of comment
-- ----------------------------
BEGIN;
INSERT INTO `comment` VALUES (50, -1, '留言测试', '2019-02-25 05:48:26', '2019-02-25 05:48:26', 1);
INSERT INTO `comment` VALUES (51, 69, '```js\nvar a = 1\n```', '2019-02-25 05:48:47', '2019-02-25 05:48:47', 1);
COMMIT;

-- ----------------------------
-- Table structure for example
-- ----------------------------
DROP TABLE IF EXISTS `example`;
CREATE TABLE `example` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `artcle` text,
  `username` varchar(20) NOT NULL,
  `password` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Table structure for examples
-- ----------------------------
DROP TABLE IF EXISTS `examples`;
CREATE TABLE `examples` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `artcle` text,
  `username` varchar(20) NOT NULL,
  `password` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for reply
-- ----------------------------
DROP TABLE IF EXISTS `reply`;
CREATE TABLE `reply` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` text,
  `createdAt` datetime DEFAULT NULL,
  `updatedAt` datetime DEFAULT NULL,
  `commentId` int(11) DEFAULT NULL,
  `articleId` int(11) DEFAULT NULL,
  `userId` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`),
  CONSTRAINT `reply_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of reply
-- ----------------------------
BEGIN;
INSERT INTO `reply` VALUES (13, '第一条回复', '2019-02-23 05:36:25', '2019-02-23 05:36:25', 41, NULL, NULL);
INSERT INTO `reply` VALUES (22, '回复测试', '2019-02-25 05:48:34', '2019-02-25 05:48:34', 50, NULL, 1);
COMMIT;

-- ----------------------------
-- Table structure for tag
-- ----------------------------
DROP TABLE IF EXISTS `tag`;
CREATE TABLE `tag` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `articleId` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `articleId` (`articleId`),
  CONSTRAINT `tag_ibfk_1` FOREIGN KEY (`articleId`) REFERENCES `article` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=140 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of tag
-- ----------------------------
BEGIN;
INSERT INTO `tag` VALUES (2, 'MySQL', 1);
INSERT INTO `tag` VALUES (4, 'MySQL', 2);
INSERT INTO `tag` VALUES (6, 'MySQL', 3);
INSERT INTO `tag` VALUES (7, 'MySQL', 4);
INSERT INTO `tag` VALUES (18, 'React', 15);
INSERT INTO `tag` VALUES (19, 'React', 16);
INSERT INTO `tag` VALUES (38, 'Sequelize', 23);
INSERT INTO `tag` VALUES (39, 'Sequelize', 24);
INSERT INTO `tag` VALUES (40, 'Sequelize', 25);
INSERT INTO `tag` VALUES (41, 'Sequelize', 26);
INSERT INTO `tag` VALUES (42, 'Sequelize', 27);
INSERT INTO `tag` VALUES (43, 'Sequelize', 28);
INSERT INTO `tag` VALUES (44, 'Vue', 29);
INSERT INTO `tag` VALUES (45, 'Vue', 30);
INSERT INTO `tag` VALUES (46, 'MVVM', 30);
INSERT INTO `tag` VALUES (47, 'Vue', 31);
INSERT INTO `tag` VALUES (49, 'Vue', 32);
INSERT INTO `tag` VALUES (51, 'ES6', 33);
INSERT INTO `tag` VALUES (52, 'ES6', 34);
INSERT INTO `tag` VALUES (53, 'ES6', 35);
INSERT INTO `tag` VALUES (54, 'Javascript', 35);
INSERT INTO `tag` VALUES (55, 'Javascript', 33);
INSERT INTO `tag` VALUES (56, 'Javascript', 34);
INSERT INTO `tag` VALUES (57, 'ES6', 36);
INSERT INTO `tag` VALUES (58, 'Javascript', 36);
INSERT INTO `tag` VALUES (59, 'HTTP', 37);
INSERT INTO `tag` VALUES (60, 'HTTP', 38);
INSERT INTO `tag` VALUES (61, 'HTTP', 39);
INSERT INTO `tag` VALUES (62, '跨域', 39);
INSERT INTO `tag` VALUES (63, 'HTTP', 40);
INSERT INTO `tag` VALUES (65, 'HTTP', 41);
INSERT INTO `tag` VALUES (66, 'HTTP', 42);
INSERT INTO `tag` VALUES (67, 'regexp', 43);
INSERT INTO `tag` VALUES (68, 'Javascript', 43);
INSERT INTO `tag` VALUES (69, 'Javascript', 44);
INSERT INTO `tag` VALUES (70, 'Javascript', 45);
INSERT INTO `tag` VALUES (71, '设计模式', 45);
INSERT INTO `tag` VALUES (72, 'Javascript', 46);
INSERT INTO `tag` VALUES (73, 'Javascript', 47);
INSERT INTO `tag` VALUES (74, 'Javascript 深入系列', 47);
INSERT INTO `tag` VALUES (75, '原型原型链', 47);
INSERT INTO `tag` VALUES (76, 'Javascript', 48);
INSERT INTO `tag` VALUES (77, 'Javascript 深入系列', 48);
INSERT INTO `tag` VALUES (78, '作用域', 48);
INSERT INTO `tag` VALUES (79, 'Javascript', 49);
INSERT INTO `tag` VALUES (80, 'Javascript 深入系列', 49);
INSERT INTO `tag` VALUES (81, '执行上下文', 49);
INSERT INTO `tag` VALUES (82, 'Javascript', 50);
INSERT INTO `tag` VALUES (83, 'Javascript 深入系列', 50);
INSERT INTO `tag` VALUES (84, 'Javascript', 51);
INSERT INTO `tag` VALUES (85, 'Javascript 深入系列', 51);
INSERT INTO `tag` VALUES (86, '原型原型链', 51);
INSERT INTO `tag` VALUES (87, 'Javascript', 52);
INSERT INTO `tag` VALUES (88, 'Javascript 深入系列', 52);
INSERT INTO `tag` VALUES (89, 'this', 52);
INSERT INTO `tag` VALUES (90, 'Javascript', 53);
INSERT INTO `tag` VALUES (91, 'Javascript 深入系列', 53);
INSERT INTO `tag` VALUES (92, '执行上下文', 53);
INSERT INTO `tag` VALUES (93, 'Javascript', 54);
INSERT INTO `tag` VALUES (94, 'Javascript 深入系列', 54);
INSERT INTO `tag` VALUES (95, '闭包', 54);
INSERT INTO `tag` VALUES (96, 'Javascript 深入系列', 55);
INSERT INTO `tag` VALUES (97, 'Javascript', 55);
INSERT INTO `tag` VALUES (98, 'Javascript 深入系列', 56);
INSERT INTO `tag` VALUES (99, 'Javascript', 56);
INSERT INTO `tag` VALUES (100, 'Javascript 深入系列', 57);
INSERT INTO `tag` VALUES (101, 'Javascript', 57);
INSERT INTO `tag` VALUES (102, 'Javascript 深入系列', 58);
INSERT INTO `tag` VALUES (103, 'Javascript', 58);
INSERT INTO `tag` VALUES (104, 'Javascript 深入系列', 59);
INSERT INTO `tag` VALUES (105, 'Javascript', 59);
INSERT INTO `tag` VALUES (106, 'Javascript 深入系列', 60);
INSERT INTO `tag` VALUES (107, 'Javascript', 60);
INSERT INTO `tag` VALUES (108, 'CSS', 61);
INSERT INTO `tag` VALUES (109, 'flex', 61);
INSERT INTO `tag` VALUES (110, 'canvas', 62);
INSERT INTO `tag` VALUES (112, 'React', 22);
INSERT INTO `tag` VALUES (113, 'React-Router', 22);
INSERT INTO `tag` VALUES (114, 'React', 21);
INSERT INTO `tag` VALUES (115, 'React-Router', 21);
INSERT INTO `tag` VALUES (116, 'React', 20);
INSERT INTO `tag` VALUES (117, 'React-Router', 20);
INSERT INTO `tag` VALUES (118, 'React', 19);
INSERT INTO `tag` VALUES (119, 'React-Router', 19);
INSERT INTO `tag` VALUES (120, 'React', 18);
INSERT INTO `tag` VALUES (121, 'React-Router', 18);
INSERT INTO `tag` VALUES (122, 'React', 17);
INSERT INTO `tag` VALUES (123, 'React-Router', 17);
INSERT INTO `tag` VALUES (124, 'React', 14);
INSERT INTO `tag` VALUES (125, 'React', 13);
INSERT INTO `tag` VALUES (126, 'React', 12);
INSERT INTO `tag` VALUES (127, 'React', 11);
INSERT INTO `tag` VALUES (128, 'React', 10);
INSERT INTO `tag` VALUES (129, 'React', 9);
INSERT INTO `tag` VALUES (130, 'React', 8);
INSERT INTO `tag` VALUES (131, 'React', 7);
INSERT INTO `tag` VALUES (132, 'React', 6);
INSERT INTO `tag` VALUES (133, 'React', 5);
INSERT INTO `tag` VALUES (134, 'webpack', 64);
INSERT INTO `tag` VALUES (135, 'webpack', 65);
INSERT INTO `tag` VALUES (136, 'webpack', 66);
INSERT INTO `tag` VALUES (137, 'webpack', 67);
INSERT INTO `tag` VALUES (138, 'tools', 68);
INSERT INTO `tag` VALUES (139, 'node', 69);
COMMIT;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `auth` tinyint(4) DEFAULT '0',
  `createdAt` datetime DEFAULT NULL,
  `updatedAt` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'admin', '$2b$10$rIIL/G.YJS1/a73MdawcHeCXnQmf9BElyWbZ.yTn5kGm8HE.bPej2', 1, '2019-02-25 05:47:02', '2019-02-25 05:47:02');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
